Refactoring package go-mc/bot
This commit is contained in:
32
bot/basic/basic.go
Normal file
32
bot/basic/basic.go
Normal file
@ -0,0 +1,32 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"github.com/Tnze/go-mc/bot"
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
type Player struct {
|
||||
c *bot.Client
|
||||
Settings Settings
|
||||
|
||||
PlayerInfo
|
||||
WorldInfo
|
||||
}
|
||||
|
||||
func NewPlayer(c *bot.Client, settings Settings) *Player {
|
||||
b := &Player{c: c, Settings: settings}
|
||||
c.Events.AddListener(
|
||||
bot.PacketHandler{Priority: 0, ID: packetid.Login, F: b.handleJoinGamePacket},
|
||||
bot.PacketHandler{Priority: 0, ID: packetid.KeepAliveClientbound, F: b.handleKeepAlivePacket},
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
func (p *Player) Respawn() error {
|
||||
const PerformRespawn = 0
|
||||
return p.c.Conn.WritePacket(pk.Marshal(
|
||||
packetid.ClientCommand,
|
||||
pk.VarInt(PerformRespawn),
|
||||
))
|
||||
}
|
83
bot/basic/events.go
Normal file
83
bot/basic/events.go
Normal file
@ -0,0 +1,83 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/Tnze/go-mc/bot"
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
type EventsListener struct {
|
||||
GameStart func() error
|
||||
ChatMsg func(c chat.Message, pos byte, uuid uuid.UUID) error
|
||||
Disconnect func(reason chat.Message) error
|
||||
HealthChange func(health float32) error
|
||||
Death func() error
|
||||
}
|
||||
|
||||
func (e EventsListener) Attach(c *bot.Client) {
|
||||
c.Events.AddListener(
|
||||
bot.PacketHandler{Priority: 64, ID: packetid.Login, F: e.onJoinGame},
|
||||
bot.PacketHandler{Priority: 64, ID: packetid.ChatClientbound, F: e.onChatMsg},
|
||||
bot.PacketHandler{Priority: 64, ID: packetid.Disconnect, F: e.onDisconnect},
|
||||
bot.PacketHandler{Priority: 64, ID: packetid.UpdateHealth, F: e.onUpdateHealth},
|
||||
)
|
||||
}
|
||||
|
||||
func (e *EventsListener) onJoinGame(_ pk.Packet) error {
|
||||
if e.GameStart != nil {
|
||||
return e.GameStart()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EventsListener) onDisconnect(p pk.Packet) error {
|
||||
if e.Disconnect != nil {
|
||||
var reason chat.Message
|
||||
if err := p.Scan(&reason); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.Disconnect(reason)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EventsListener) onChatMsg(p pk.Packet) error {
|
||||
if e.ChatMsg != nil {
|
||||
var msg chat.Message
|
||||
var pos pk.Byte
|
||||
var sender pk.UUID
|
||||
|
||||
if err := p.Scan(&msg, &pos, &sender); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return e.ChatMsg(msg, byte(pos), uuid.UUID(sender))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EventsListener) onUpdateHealth(p pk.Packet) error {
|
||||
if e.ChatMsg != nil {
|
||||
var health pk.Float
|
||||
var food pk.VarInt
|
||||
var foodSaturation pk.Float
|
||||
|
||||
if err := p.Scan(&health, &food, &foodSaturation); err != nil {
|
||||
return err
|
||||
}
|
||||
if e.HealthChange != nil {
|
||||
if err := e.HealthChange(float32(health)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if e.Death != nil && health <= 0 {
|
||||
if err := e.Death(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
79
bot/basic/info.go
Normal file
79
bot/basic/info.go
Normal file
@ -0,0 +1,79 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// WorldInfo content player info in server.
|
||||
type WorldInfo struct {
|
||||
DimensionCodec struct {
|
||||
DimensionType interface{} `nbt:"minecraft:dimension_type"`
|
||||
WorldgenBiome interface{} `nbt:"minecraft:worldgen/biome"`
|
||||
}
|
||||
Dimension interface{}
|
||||
WorldNames []string // Identifiers for all worlds on the server.
|
||||
WorldName string // Name of the world being spawned into.
|
||||
HashedSeed int64 // First 8 bytes of the SHA-256 hash of the world's seed. Used client side for biome noise
|
||||
MaxPlayers int32 // Was once used by the client to draw the player list, but now is ignored.
|
||||
ViewDistance int32 // Render distance (2-32).
|
||||
ReducedDebugInfo bool // If true, a Notchian client shows reduced information on the debug screen. For servers in development, this should almost always be false.
|
||||
EnableRespawnScreen bool // Set to false when the doImmediateRespawn gamerule is true.
|
||||
IsDebug bool // True if the world is a debug mode world; debug mode worlds cannot be modified and have predefined blocks.
|
||||
IsFlat bool // True if the world is a superflat world; flat worlds have different void fog and a horizon at y=0 instead of y=63.
|
||||
}
|
||||
|
||||
type PlayerInfo struct {
|
||||
EID int32 // The player's Entity ID (EID).
|
||||
Hardcore bool // Is hardcore
|
||||
Gamemode byte // Gamemode. 0: Survival, 1: Creative, 2: Adventure, 3: Spectator.
|
||||
PrevGamemode int8 // Previous Gamemode
|
||||
}
|
||||
|
||||
// ServInfo contains information about the server implementation.
|
||||
type ServInfo struct {
|
||||
Brand string
|
||||
}
|
||||
|
||||
func (p *Player) handleJoinGamePacket(packet pk.Packet) error {
|
||||
var WorldCount pk.VarInt
|
||||
var WorldNames = []pk.Identifier{}
|
||||
err := packet.Scan(
|
||||
(*pk.Int)(&p.EID),
|
||||
(*pk.Boolean)(&p.Hardcore),
|
||||
(*pk.UnsignedByte)(&p.Gamemode),
|
||||
(*pk.Byte)(&p.PrevGamemode),
|
||||
&WorldCount,
|
||||
pk.Ary{Len: &WorldCount, Ary: &WorldNames},
|
||||
&pk.NBT{V: new(interface{})},
|
||||
&pk.NBT{V: new(interface{})},
|
||||
(*pk.Identifier)(&p.WorldName),
|
||||
(*pk.Long)(&p.HashedSeed),
|
||||
(*pk.VarInt)(&p.MaxPlayers),
|
||||
(*pk.VarInt)(&p.ViewDistance),
|
||||
(*pk.Boolean)(&p.ReducedDebugInfo),
|
||||
(*pk.Boolean)(&p.EnableRespawnScreen),
|
||||
(*pk.Boolean)(&p.IsDebug),
|
||||
(*pk.Boolean)(&p.IsFlat),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// This line should work "like" the following code but without copy things
|
||||
// p.WorldNames = make([]string, len(WorldNames))
|
||||
// for i, v := range WorldNames {
|
||||
// p.WorldNames[i] = string(v)
|
||||
// }
|
||||
p.WorldNames = *(*[]string)(unsafe.Pointer(&WorldNames))
|
||||
|
||||
return p.c.Conn.WritePacket(
|
||||
//PluginMessage packet (serverbound) - sending minecraft brand.
|
||||
pk.Marshal(
|
||||
packetid.CustomPayloadServerbound,
|
||||
pk.Identifier("minecraft:brand"),
|
||||
pk.String(p.Settings.Brand),
|
||||
),
|
||||
)
|
||||
}
|
18
bot/basic/keepalive.go
Normal file
18
bot/basic/keepalive.go
Normal file
@ -0,0 +1,18 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
func (p Player) handleKeepAlivePacket(packet pk.Packet) error {
|
||||
var KeepAliveID pk.Long
|
||||
if err := packet.Scan(&KeepAliveID); err != nil {
|
||||
return err
|
||||
}
|
||||
// Response
|
||||
return p.c.Conn.WritePacket(pk.Marshal(
|
||||
packetid.KeepAliveServerbound,
|
||||
KeepAliveID,
|
||||
))
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package bot
|
||||
package basic
|
||||
|
||||
// Settings of client
|
||||
type Settings struct {
|
@ -1,56 +1,24 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Tnze/go-mc/bot/path"
|
||||
"github.com/Tnze/go-mc/bot/phy"
|
||||
"github.com/Tnze/go-mc/bot/world"
|
||||
"github.com/Tnze/go-mc/bot/world/entity"
|
||||
"github.com/Tnze/go-mc/bot/world/entity/player"
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
"github.com/Tnze/go-mc/net"
|
||||
"github.com/Tnze/go-mc/net/packet"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Client is used to access Minecraft server
|
||||
type Client struct {
|
||||
conn *net.Conn
|
||||
Auth
|
||||
Conn *net.Conn
|
||||
Auth Auth
|
||||
|
||||
player.Player
|
||||
PlayInfo
|
||||
ServInfo
|
||||
abilities PlayerAbilities
|
||||
settings Settings
|
||||
Name string
|
||||
UUID uuid.UUID
|
||||
|
||||
Wd world.World //the map data
|
||||
Inputs path.Inputs
|
||||
Physics phy.State
|
||||
lastPosTx time.Time
|
||||
justTeleported bool
|
||||
|
||||
// Delegate allows you push a function to let HandleGame run.
|
||||
// Do not send at the same goroutine!
|
||||
Delegate chan func() error
|
||||
Events eventBroker
|
||||
|
||||
closing chan struct{}
|
||||
inbound chan pk.Packet
|
||||
wg sync.WaitGroup
|
||||
Events Events
|
||||
//TODO: LoginEvents Events
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
close(c.closing)
|
||||
err := c.disconnect()
|
||||
c.wg.Wait()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) SendCloseWindow(windowID byte) error {
|
||||
return c.conn.WritePacket(packet.Marshal(packetid.CloseWindowServerbound, pk.UnsignedByte(windowID)))
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
// NewClient init and return a new Client.
|
||||
@ -62,44 +30,11 @@ func (c *Client) SendCloseWindow(windowID byte) error {
|
||||
// and load your Name, UUID and AccessToken to client.
|
||||
func NewClient() *Client {
|
||||
return &Client{
|
||||
settings: DefaultSettings,
|
||||
Auth: Auth{Name: "Steve"},
|
||||
Delegate: make(chan func() error),
|
||||
Wd: world.World{
|
||||
Entities: make(map[int32]*entity.Entity, 8192),
|
||||
Chunks: make(map[world.ChunkLoc]*world.Chunk, 2048),
|
||||
},
|
||||
closing: make(chan struct{}),
|
||||
inbound: make(chan pk.Packet, 5),
|
||||
Auth: Auth{Name: "Steve"},
|
||||
Events: Events{handlers: make(map[int32]*handlerHeap)},
|
||||
}
|
||||
}
|
||||
|
||||
//PlayInfo content player info in server.
|
||||
type PlayInfo struct {
|
||||
Gamemode int //游戏模式
|
||||
Hardcore bool //是否是极限模式
|
||||
Dimension int //维度
|
||||
Difficulty int //难度
|
||||
ViewDistance int //视距
|
||||
ReducedDebugInfo bool //减少调试信息
|
||||
WorldName string //当前世界的名字
|
||||
IsDebug bool //调试
|
||||
IsFlat bool //超平坦世界
|
||||
SpawnPosition Position //主世界出生点
|
||||
}
|
||||
|
||||
// ServInfo contains information about the server implementation.
|
||||
type ServInfo struct {
|
||||
Brand string
|
||||
}
|
||||
|
||||
// PlayerAbilities defines what player can do.
|
||||
type PlayerAbilities struct {
|
||||
Flags int8
|
||||
FlyingSpeed float32
|
||||
FieldofViewModifier float32
|
||||
}
|
||||
|
||||
//Position is a 3D vector.
|
||||
type Position struct {
|
||||
X, Y, Z int
|
||||
|
111
bot/event.go
111
bot/event.go
@ -1,89 +1,42 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"github.com/Tnze/go-mc/bot/world/entity"
|
||||
"github.com/Tnze/go-mc/bot/world/entity/player"
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
"github.com/google/uuid"
|
||||
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/Tnze/go-mc/net/ptypes"
|
||||
)
|
||||
|
||||
type seenPacketFlags uint8
|
||||
|
||||
// Valid seenPacketFlags values.
|
||||
const (
|
||||
seenJoinGame seenPacketFlags = 1 << iota
|
||||
seenServerDifficulty
|
||||
seenPlayerAbilities
|
||||
seenPlayerInventory
|
||||
seenUpdateLight
|
||||
seenChunkData
|
||||
seenPlayerPositionAndLook
|
||||
seenSpawnPos
|
||||
|
||||
// gameReadyMinPackets are the minimum set of packets that must be seen, for
|
||||
// the GameReady callback to be invoked.
|
||||
gameReadyMinPackets = seenJoinGame | seenChunkData | seenUpdateLight |
|
||||
seenPlayerAbilities | seenPlayerInventory | seenServerDifficulty |
|
||||
seenPlayerPositionAndLook | seenSpawnPos
|
||||
)
|
||||
|
||||
type eventBroker struct {
|
||||
seenPackets seenPacketFlags
|
||||
isReady bool
|
||||
|
||||
GameStart func() error
|
||||
ChatMsg func(msg chat.Message, pos byte, sender uuid.UUID) error
|
||||
Disconnect func(reason chat.Message) error
|
||||
HealthChange func() error
|
||||
Die func() error
|
||||
SoundPlay func(name string, category int, x, y, z float64, volume, pitch float32) error
|
||||
PluginMessage func(channel string, data []byte) error
|
||||
HeldItemChange func(slot int) error
|
||||
OpenWindow func(pkt ptypes.OpenWindow) error
|
||||
|
||||
// ExperienceChange will be called every time player's experience level updates.
|
||||
// Parameters:
|
||||
// bar - state of the experience bar from 0.0 to 1.0;
|
||||
// level - current level;
|
||||
// total - total amount of experience received from level 0.
|
||||
ExperienceChange func(bar float32, level int32, total int32) error
|
||||
|
||||
WindowsItem func(id byte, slots []entity.Slot) error
|
||||
WindowsItemChange func(id byte, slotID int, slot entity.Slot) error
|
||||
WindowConfirmation func(pkt ptypes.ConfirmTransaction) error
|
||||
|
||||
// ServerDifficultyChange is called whenever the gamemode of the server changes.
|
||||
// At time of writing (1.16.3), difficulty values of 0, 1, 2, and 3 correspond
|
||||
// to peaceful, easy, normal, and hard respectively.
|
||||
ServerDifficultyChange func(difficulty int) error
|
||||
|
||||
// GameReady is called after the client has joined the server and successfully
|
||||
// received player state. Additionally, the server has begun sending world
|
||||
// state (such as lighting and chunk information).
|
||||
//
|
||||
// Use this callback as a signal as to when your bot should start 'doing'
|
||||
// things.
|
||||
GameReady func() error
|
||||
|
||||
// PositionChange is called whenever the player position is updated.
|
||||
PositionChange func(pos player.Pos) error
|
||||
|
||||
// ReceivePacket will be called when new packets arrive.
|
||||
// The default handler will run only if pass == false.
|
||||
ReceivePacket func(p pk.Packet) (pass bool, err error)
|
||||
|
||||
// PrePhysics will be called before a physics tick.
|
||||
PrePhysics func() error
|
||||
type Events struct {
|
||||
handlers map[int32]*handlerHeap
|
||||
}
|
||||
|
||||
func (b *eventBroker) updateSeenPackets(f seenPacketFlags) error {
|
||||
b.seenPackets |= f
|
||||
if (^b.seenPackets)&gameReadyMinPackets == 0 && b.GameReady != nil && !b.isReady {
|
||||
b.isReady = true
|
||||
return b.GameReady()
|
||||
func (e *Events) AddListener(listeners ...PacketHandler) {
|
||||
for _, l := range listeners {
|
||||
var s *handlerHeap
|
||||
var ok bool
|
||||
if s, ok = e.handlers[l.ID]; !ok {
|
||||
s = &handlerHeap{l}
|
||||
e.handlers[l.ID] = s
|
||||
}
|
||||
s.Push(l)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PacketHandlerFunc func(p pk.Packet) error
|
||||
type PacketHandler struct {
|
||||
ID int32
|
||||
Priority int
|
||||
F func(p pk.Packet) error
|
||||
}
|
||||
|
||||
// handlerHeap is PriorityQueue<PacketHandlerFunc>
|
||||
type handlerHeap []PacketHandler
|
||||
|
||||
func (h handlerHeap) Len() int { return len(h) }
|
||||
func (h handlerHeap) Less(i, j int) bool { return h[i].Priority < h[j].Priority }
|
||||
func (h handlerHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||
func (h *handlerHeap) Push(x interface{}) { *h = append(*h, x.(PacketHandler)) }
|
||||
func (h *handlerHeap) Pop() interface{} {
|
||||
old := *h
|
||||
n := len(old)
|
||||
*h = old[0 : n-1]
|
||||
return old[n-1]
|
||||
}
|
||||
|
793
bot/ingame.go
793
bot/ingame.go
@ -1,784 +1,49 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/Tnze/go-mc/bot/world"
|
||||
"github.com/Tnze/go-mc/bot/world/entity"
|
||||
"github.com/Tnze/go-mc/bot/world/entity/player"
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
"github.com/Tnze/go-mc/data/soundid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/Tnze/go-mc/net/ptypes"
|
||||
)
|
||||
|
||||
func (c *Client) updateServerPos(pos player.Pos) (err error) {
|
||||
prev := c.Player.Pos
|
||||
c.Player.Pos = pos
|
||||
|
||||
switch {
|
||||
case !prev.LookEqual(pos) && !prev.PosEqual(pos):
|
||||
err = sendPlayerPositionAndLookPacket(c)
|
||||
case !prev.PosEqual(pos):
|
||||
err = sendPlayerPositionPacket(c)
|
||||
case !prev.LookEqual(pos):
|
||||
err = sendPlayerLookPacket(c)
|
||||
case prev.OnGround != pos.OnGround:
|
||||
err = c.conn.WritePacket(
|
||||
pk.Marshal(
|
||||
packetid.Flying,
|
||||
pk.Boolean(pos.OnGround),
|
||||
),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.justTeleported || time.Now().Add(-time.Second).After(c.lastPosTx) {
|
||||
c.justTeleported = false
|
||||
c.lastPosTx = time.Now()
|
||||
err = sendPlayerPositionPacket(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.Events.PositionChange != nil && !prev.Equal(pos) {
|
||||
if err := c.Events.PositionChange(pos); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleGame receive server packet and response them correctly.
|
||||
// Note that HandleGame will block if you don't receive from Events.
|
||||
func (c *Client) HandleGame() error {
|
||||
c.wg.Add(1)
|
||||
go func() {
|
||||
defer c.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-c.closing:
|
||||
return
|
||||
|
||||
default:
|
||||
var p pk.Packet
|
||||
//Read packets
|
||||
if err := c.conn.ReadPacket(&p); err != nil {
|
||||
return
|
||||
}
|
||||
c.inbound <- p
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
cTick := time.NewTicker(time.Second / 10 / 2)
|
||||
defer cTick.Stop()
|
||||
var p pk.Packet
|
||||
for {
|
||||
select {
|
||||
case <-c.closing:
|
||||
return http.ErrServerClosed
|
||||
case <-cTick.C:
|
||||
if c.Events.PrePhysics != nil {
|
||||
if err := c.Events.PrePhysics(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := c.Physics.Tick(c.Inputs, &c.Wd); err != nil {
|
||||
c.disconnect()
|
||||
return err
|
||||
}
|
||||
c.updateServerPos(c.Physics.Position())
|
||||
//Read packets
|
||||
if err := c.Conn.ReadPacket(&p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case task := <-c.Delegate:
|
||||
if err := task(); err != nil {
|
||||
c.disconnect()
|
||||
return err
|
||||
}
|
||||
case p := <-c.inbound:
|
||||
//handle packets
|
||||
disconnect, err := c.handlePacket(p)
|
||||
if err != nil {
|
||||
c.disconnect()
|
||||
return fmt.Errorf("handle packet 0x%X error: %w", p.ID, err)
|
||||
}
|
||||
if disconnect {
|
||||
return nil
|
||||
}
|
||||
//handle packets
|
||||
err := c.handlePacket(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) handlePacket(p pk.Packet) (disconnect bool, err error) {
|
||||
if c.Events.ReceivePacket != nil {
|
||||
pass, err := c.Events.ReceivePacket(p)
|
||||
if err != nil {
|
||||
return false, err
|
||||
type PacketHandlerError struct {
|
||||
ID int32
|
||||
Err error
|
||||
}
|
||||
|
||||
func (d PacketHandlerError) Error() string {
|
||||
return fmt.Sprintf("handle packet 0x%X error: %v", d.ID, d.Err)
|
||||
}
|
||||
|
||||
func (d PacketHandlerError) Unwrap() error {
|
||||
return d.Err
|
||||
}
|
||||
|
||||
func (c *Client) handlePacket(p pk.Packet) (err error) {
|
||||
if listeners := c.Events.handlers[p.ID]; listeners != nil {
|
||||
for _, handler := range *listeners {
|
||||
err = handler.F(p)
|
||||
if err != nil {
|
||||
return PacketHandlerError{ID: p.ID, Err: err}
|
||||
}
|
||||
}
|
||||
if pass {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
switch p.ID {
|
||||
case packetid.Login:
|
||||
err = handleJoinGamePacket(c, p)
|
||||
case packetid.CustomPayloadClientbound:
|
||||
err = handlePluginPacket(c, p)
|
||||
case packetid.Difficulty:
|
||||
err = handleServerDifficultyPacket(c, p)
|
||||
case packetid.SpawnPosition:
|
||||
err = handleSpawnPositionPacket(c, p)
|
||||
if err2 := c.Events.updateSeenPackets(seenSpawnPos); err == nil {
|
||||
err = err2
|
||||
}
|
||||
case packetid.AbilitiesClientbound:
|
||||
err = handlePlayerAbilitiesPacket(c, p)
|
||||
case packetid.UpdateHealth:
|
||||
err = handleUpdateHealthPacket(c, p)
|
||||
case packetid.ChatClientbound:
|
||||
err = handleChatMessagePacket(c, p)
|
||||
|
||||
case packetid.HeldItemSlotClientbound:
|
||||
err = handleHeldItemPacket(c, p)
|
||||
case packetid.WindowItems:
|
||||
err = handleWindowItemsPacket(c, p)
|
||||
case packetid.OpenWindow:
|
||||
err = handleOpenWindowPacket(c, p)
|
||||
case packetid.TransactionClientbound:
|
||||
err = handleWindowConfirmationPacket(c, p)
|
||||
|
||||
case packetid.DeclareRecipes:
|
||||
// handleDeclareRecipesPacket(g, reader)
|
||||
case packetid.KeepAliveClientbound:
|
||||
err = handleKeepAlivePacket(c, p)
|
||||
|
||||
case packetid.SpawnEntity:
|
||||
err = handleSpawnEntityPacket(c, p)
|
||||
case packetid.NamedEntitySpawn:
|
||||
err = handleSpawnPlayerPacket(c, p)
|
||||
case packetid.SpawnEntityLiving:
|
||||
err = handleSpawnLivingEntityPacket(c, p)
|
||||
case packetid.Animation:
|
||||
err = handleEntityAnimationPacket(c, p)
|
||||
case packetid.EntityStatus:
|
||||
err = handleEntityStatusPacket(c, p)
|
||||
case packetid.EntityDestroy:
|
||||
err = handleDestroyEntitiesPacket(c, p)
|
||||
case packetid.RelEntityMove:
|
||||
err = handleEntityPositionPacket(c, p)
|
||||
case packetid.EntityMoveLook:
|
||||
err = handleEntityPositionLookPacket(c, p)
|
||||
case packetid.EntityLook:
|
||||
err = handleEntityLookPacket(c, p)
|
||||
case packetid.Entity:
|
||||
err = handleEntityMovePacket(c, p)
|
||||
|
||||
case packetid.UpdateLight:
|
||||
err = c.Events.updateSeenPackets(seenUpdateLight)
|
||||
case packetid.MapChunk:
|
||||
err = handleChunkDataPacket(c, p)
|
||||
case packetid.BlockChange:
|
||||
err = handleBlockChangePacket(c, p)
|
||||
case packetid.MultiBlockChange:
|
||||
err = handleMultiBlockChangePacket(c, p)
|
||||
case packetid.UnloadChunk:
|
||||
err = handleUnloadChunkPacket(c, p)
|
||||
case packetid.TileEntityData:
|
||||
err = handleTileEntityDataPacket(c, p)
|
||||
|
||||
case packetid.PositionClientbound:
|
||||
err = handlePlayerPositionAndLookPacket(c, p)
|
||||
sendPlayerPositionAndLookPacket(c) // to confirm the position
|
||||
if err2 := c.Events.updateSeenPackets(seenPlayerPositionAndLook); err == nil {
|
||||
err = err2
|
||||
}
|
||||
|
||||
case packetid.KickDisconnect:
|
||||
err = handleDisconnectPacket(c, p)
|
||||
disconnect = true
|
||||
case packetid.SetSlot:
|
||||
err = handleSetSlotPacket(c, p)
|
||||
case packetid.SoundEffect:
|
||||
err = handleSoundEffect(c, p)
|
||||
case packetid.NamedSoundEffect:
|
||||
err = handleNamedSoundEffect(c, p)
|
||||
case packetid.Experience:
|
||||
err = handleSetExperience(c, p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func handleSpawnEntityPacket(c *Client, p pk.Packet) error {
|
||||
var se ptypes.SpawnEntity
|
||||
if err := se.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Wd.OnSpawnEntity(se)
|
||||
}
|
||||
|
||||
func handleSpawnLivingEntityPacket(c *Client, p pk.Packet) error {
|
||||
var se ptypes.SpawnLivingEntity
|
||||
if err := se.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Wd.OnSpawnLivingEntity(se)
|
||||
}
|
||||
|
||||
func handleSpawnPlayerPacket(c *Client, p pk.Packet) error {
|
||||
var se ptypes.SpawnPlayer
|
||||
if err := se.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Wd.OnSpawnPlayer(se)
|
||||
}
|
||||
|
||||
func handleEntityPositionPacket(c *Client, p pk.Packet) error {
|
||||
var pu ptypes.EntityPosition
|
||||
if err := pu.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Wd.OnEntityPosUpdate(pu)
|
||||
}
|
||||
|
||||
func handleEntityPositionLookPacket(c *Client, p pk.Packet) error {
|
||||
var epr ptypes.EntityPositionLook
|
||||
if err := epr.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Wd.OnEntityPosLookUpdate(epr)
|
||||
}
|
||||
|
||||
func handleEntityLookPacket(c *Client, p pk.Packet) error {
|
||||
var er ptypes.EntityRotation
|
||||
if err := er.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Wd.OnEntityLookUpdate(er)
|
||||
}
|
||||
|
||||
func handleEntityMovePacket(c *Client, p pk.Packet) error {
|
||||
var id pk.VarInt
|
||||
if err := p.Scan(&id); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleEntityAnimationPacket(c *Client, p pk.Packet) error {
|
||||
var se ptypes.EntityAnimationClientbound
|
||||
if err := se.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleEntityStatusPacket(c *Client, p pk.Packet) error {
|
||||
var (
|
||||
id pk.Int
|
||||
status pk.Byte
|
||||
)
|
||||
if err := p.Scan(&id, &status); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleDestroyEntitiesPacket(c *Client, p pk.Packet) error {
|
||||
var count pk.VarInt
|
||||
var data = pk.Ary{
|
||||
Len: &count,
|
||||
Ary: []pk.VarInt{},
|
||||
}
|
||||
if err := p.Scan(&count, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Wd.OnEntityDestroy(data.Ary.([]pk.VarInt))
|
||||
}
|
||||
|
||||
func handleSoundEffect(c *Client, p pk.Packet) error {
|
||||
var s ptypes.SoundEffect
|
||||
if err := s.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Events.SoundPlay != nil {
|
||||
if soundName, ok := soundid.GetSoundNameByID(soundid.SoundID(s.Sound)); ok {
|
||||
return c.Events.SoundPlay(
|
||||
soundName, int(s.Category),
|
||||
float64(s.X)/8, float64(s.Y)/8, float64(s.Z)/8,
|
||||
float32(s.Volume), float32(s.Pitch))
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "WARN: Unknown sound name (%v). is data.SoundNames out of date?\n", s.Sound)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleNamedSoundEffect(c *Client, p pk.Packet) error {
|
||||
var s ptypes.NamedSoundEffect
|
||||
if err := s.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Events.SoundPlay != nil {
|
||||
return c.Events.SoundPlay(
|
||||
string(s.Sound), int(s.Category),
|
||||
float64(s.X)/8, float64(s.Y)/8, float64(s.Z)/8,
|
||||
float32(s.Volume), float32(s.Pitch))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleDisconnectPacket(c *Client, p pk.Packet) error {
|
||||
var reason chat.Message
|
||||
|
||||
err := p.Scan(&reason)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Events.Disconnect != nil {
|
||||
return c.Events.Disconnect(reason)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleSetSlotPacket(c *Client, p pk.Packet) error {
|
||||
if c.Events.WindowsItemChange == nil {
|
||||
return nil
|
||||
}
|
||||
var pkt ptypes.SetSlot
|
||||
if err := pkt.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Events.WindowsItemChange(byte(pkt.WindowID), int(pkt.Slot), pkt.SlotData)
|
||||
}
|
||||
|
||||
func handleMultiBlockChangePacket(c *Client, p pk.Packet) error {
|
||||
if !c.settings.ReceiveMap {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
loc pk.Long
|
||||
dontTrustEdges pk.Boolean
|
||||
sz pk.VarInt
|
||||
packedBlocks = pk.Ary{
|
||||
Len: &sz,
|
||||
Ary: []pk.VarLong{},
|
||||
}
|
||||
)
|
||||
err := p.Scan(&loc, &dontTrustEdges, &sz, &packedBlocks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
x := int((loc >> 42) & ((1 << 22) - 1))
|
||||
y := int((loc >> 20) & ((1 << 22) - 1))
|
||||
z := int(loc & ((1 << 20) - 1))
|
||||
|
||||
// Apply transform into negative (these numbers are signed)
|
||||
if x >= 1<<21 {
|
||||
x -= 1 << 22
|
||||
}
|
||||
if z >= 1<<21 {
|
||||
z -= 1 << 22
|
||||
}
|
||||
|
||||
c.Wd.MultiBlockUpdate(world.ChunkLoc{X: x, Z: z}, y, packedBlocks.Ary.([]pk.VarLong))
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleBlockChangePacket(c *Client, p pk.Packet) error {
|
||||
if !c.settings.ReceiveMap {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
pos pk.Position
|
||||
bID pk.VarInt
|
||||
)
|
||||
|
||||
if err := p.Scan(&pos, &bID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Wd.UnaryBlockUpdate(pos, world.BlockStatus(bID))
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleChatMessagePacket(c *Client, p pk.Packet) (err error) {
|
||||
var msg ptypes.ChatMessageClientbound
|
||||
if err := msg.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Events.ChatMsg != nil {
|
||||
return c.Events.ChatMsg(msg.S, byte(msg.Pos), uuid.UUID(msg.Sender))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleUpdateHealthPacket(c *Client, p pk.Packet) error {
|
||||
var pkt ptypes.UpdateHealth
|
||||
if err := pkt.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Health = float32(pkt.Health)
|
||||
c.Food = int32(pkt.Food)
|
||||
c.FoodSaturation = float32(pkt.FoodSaturation)
|
||||
|
||||
if c.Events.HealthChange != nil {
|
||||
if err := c.Events.HealthChange(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.Health <= 0 { //player is dead
|
||||
c.Physics.Run = false
|
||||
sendPlayerPositionAndLookPacket(c)
|
||||
if c.Events.Die != nil {
|
||||
if err := c.Events.Die(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleJoinGamePacket(c *Client, p pk.Packet) error {
|
||||
var pkt ptypes.JoinGame
|
||||
if err := pkt.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Player.ID = int32(pkt.PlayerEntity)
|
||||
c.Gamemode = int(pkt.Gamemode & 0x7)
|
||||
c.Hardcore = pkt.Gamemode&0x8 != 0
|
||||
c.Dimension = int(pkt.Dimension)
|
||||
c.WorldName = string(pkt.WorldName)
|
||||
c.ViewDistance = int(pkt.ViewDistance)
|
||||
c.ReducedDebugInfo = bool(pkt.RDI)
|
||||
c.IsDebug = bool(pkt.IsDebug)
|
||||
c.IsFlat = bool(pkt.IsFlat)
|
||||
|
||||
if c.Events.GameStart != nil {
|
||||
if err := c.Events.GameStart(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.conn.WritePacket(
|
||||
//PluginMessage packet (serverbound) - sending minecraft brand.
|
||||
pk.Marshal(
|
||||
packetid.CustomPayloadServerbound,
|
||||
pk.Identifier("minecraft:brand"),
|
||||
pk.String(c.settings.Brand),
|
||||
),
|
||||
)
|
||||
if err := c.Events.updateSeenPackets(seenJoinGame); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handlePluginPacket(c *Client, p pk.Packet) error {
|
||||
var msg ptypes.PluginMessage
|
||||
if err := msg.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch msg.Channel {
|
||||
case "minecraft:brand":
|
||||
var brandRaw pk.String
|
||||
if _, err := brandRaw.ReadFrom(bytes.NewReader(msg.Data)); err != nil {
|
||||
return err
|
||||
}
|
||||
c.ServInfo.Brand = string(brandRaw)
|
||||
}
|
||||
|
||||
if c.Events.PluginMessage != nil {
|
||||
return c.Events.PluginMessage(string(msg.Channel), msg.Data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleServerDifficultyPacket(c *Client, p pk.Packet) error {
|
||||
var difficulty pk.Byte
|
||||
if err := p.Scan(&difficulty); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Difficulty = int(difficulty)
|
||||
|
||||
if c.Events.ServerDifficultyChange != nil {
|
||||
if err := c.Events.ServerDifficultyChange(c.Difficulty); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return c.Events.updateSeenPackets(seenServerDifficulty)
|
||||
}
|
||||
|
||||
func handleSpawnPositionPacket(c *Client, p pk.Packet) error {
|
||||
var pos pk.Position
|
||||
err := p.Scan(&pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.SpawnPosition.X, c.SpawnPosition.Y, c.SpawnPosition.Z =
|
||||
pos.X, pos.Y, pos.Z
|
||||
return nil
|
||||
}
|
||||
|
||||
func handlePlayerAbilitiesPacket(c *Client, p pk.Packet) error {
|
||||
var (
|
||||
flags pk.Byte
|
||||
flySpeed pk.Float
|
||||
viewMod pk.Float
|
||||
)
|
||||
err := p.Scan(&flags, &flySpeed, &viewMod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.abilities.Flags = int8(flags)
|
||||
c.abilities.FlyingSpeed = float32(flySpeed)
|
||||
c.abilities.FieldofViewModifier = float32(viewMod)
|
||||
|
||||
c.conn.WritePacket(
|
||||
//ClientSettings packet (serverbound)
|
||||
pk.Marshal(
|
||||
packetid.Settings,
|
||||
pk.String(c.settings.Locale),
|
||||
pk.Byte(c.settings.ViewDistance),
|
||||
pk.VarInt(c.settings.ChatMode),
|
||||
pk.Boolean(c.settings.ChatColors),
|
||||
pk.UnsignedByte(c.settings.DisplayedSkinParts),
|
||||
pk.VarInt(c.settings.MainHand),
|
||||
),
|
||||
)
|
||||
return c.Events.updateSeenPackets(seenPlayerAbilities)
|
||||
}
|
||||
|
||||
func handleHeldItemPacket(c *Client, p pk.Packet) error {
|
||||
var hi pk.Byte
|
||||
if err := p.Scan(&hi); err != nil {
|
||||
return err
|
||||
}
|
||||
c.HeldItem = int(hi)
|
||||
|
||||
if c.Events.HeldItemChange != nil {
|
||||
return c.Events.HeldItemChange(c.HeldItem)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleUnloadChunkPacket(c *Client, p pk.Packet) error {
|
||||
if !c.settings.ReceiveMap {
|
||||
return nil
|
||||
}
|
||||
|
||||
var x, z pk.Int
|
||||
if err := p.Scan(&x, &z); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Wd.UnloadChunk(world.ChunkLoc{X: int(x) >> 4, Z: int(z) >> 4})
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleChunkDataPacket(c *Client, p pk.Packet) error {
|
||||
if err := c.Events.updateSeenPackets(seenChunkData); err != nil {
|
||||
return err
|
||||
}
|
||||
if !c.settings.ReceiveMap {
|
||||
return nil
|
||||
}
|
||||
|
||||
var pkt ptypes.ChunkData
|
||||
if _, err := pkt.ReadFrom(bytes.NewReader(p.Data)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chunk, err := world.DecodeChunkColumn(int32(pkt.PrimaryBitMask), pkt.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode chunk column: %w", err)
|
||||
}
|
||||
chunk.TileEntities = make(map[world.TilePosition]entity.BlockEntity, 64)
|
||||
for _, e := range pkt.BlockEntities {
|
||||
chunk.TileEntities[world.ToTilePos(e.X, e.Y, e.Z)] = e
|
||||
}
|
||||
|
||||
c.Wd.LoadChunk(int(pkt.X), int(pkt.Z), chunk)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleTileEntityDataPacket(c *Client, p pk.Packet) error {
|
||||
var pkt ptypes.TileEntityData
|
||||
if _, err := pkt.ReadFrom(bytes.NewReader(p.Data)); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Wd.TileEntityUpdate(pkt)
|
||||
}
|
||||
|
||||
func handlePlayerPositionAndLookPacket(c *Client, p pk.Packet) error {
|
||||
var pkt ptypes.PositionAndLookClientbound
|
||||
if err := pkt.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pp := c.Player.Pos
|
||||
if pkt.RelativeX() {
|
||||
pp.X += float64(pkt.X)
|
||||
} else {
|
||||
pp.X = float64(pkt.X)
|
||||
}
|
||||
if pkt.RelativeY() {
|
||||
pp.Y += float64(pkt.Y)
|
||||
} else {
|
||||
pp.Y = float64(pkt.Y)
|
||||
}
|
||||
if pkt.RelativeZ() {
|
||||
pp.Z += float64(pkt.Z)
|
||||
} else {
|
||||
pp.Z = float64(pkt.Z)
|
||||
}
|
||||
if pkt.RelativeYaw() {
|
||||
pp.Yaw += float32(pkt.Yaw)
|
||||
} else {
|
||||
pp.Yaw = float32(pkt.Yaw)
|
||||
}
|
||||
if pkt.RelativePitch() {
|
||||
pp.Pitch += float32(pkt.Pitch)
|
||||
} else {
|
||||
pp.Pitch = float32(pkt.Pitch)
|
||||
}
|
||||
if err := c.Physics.ServerPositionUpdate(pp, &c.Wd); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Player.Pos = pp
|
||||
c.justTeleported = true
|
||||
|
||||
if c.Events.PositionChange != nil {
|
||||
if err := c.Events.PositionChange(pp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//Confirm
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
packetid.TeleportConfirm,
|
||||
pk.VarInt(pkt.TeleportID),
|
||||
))
|
||||
}
|
||||
|
||||
func handleKeepAlivePacket(c *Client, p pk.Packet) error {
|
||||
var KeepAliveID pk.Long
|
||||
if err := p.Scan(&KeepAliveID); err != nil {
|
||||
return err
|
||||
}
|
||||
//Response
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
packetid.KeepAliveServerbound,
|
||||
KeepAliveID,
|
||||
))
|
||||
}
|
||||
|
||||
func handleWindowItemsPacket(c *Client, p pk.Packet) error {
|
||||
var pkt ptypes.WindowItems
|
||||
if _, err := pkt.ReadFrom(bytes.NewReader(p.Data)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pkt.WindowID == 0 { // Window ID 0 is the players' inventory.
|
||||
if err := c.Events.updateSeenPackets(seenPlayerInventory); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.Events.WindowsItem != nil {
|
||||
return c.Events.WindowsItem(byte(pkt.WindowID), pkt.Slots)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleOpenWindowPacket(c *Client, p pk.Packet) error {
|
||||
var pkt ptypes.OpenWindow
|
||||
if err := pkt.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Events.OpenWindow != nil {
|
||||
return c.Events.OpenWindow(pkt)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleWindowConfirmationPacket(c *Client, p pk.Packet) error {
|
||||
var pkt ptypes.ConfirmTransaction
|
||||
if err := pkt.Decode(p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Events.WindowConfirmation != nil {
|
||||
return c.Events.WindowConfirmation(pkt)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleSetExperience(c *Client, p pk.Packet) (err error) {
|
||||
var (
|
||||
bar pk.Float
|
||||
level pk.VarInt
|
||||
total pk.VarInt
|
||||
)
|
||||
|
||||
if err := p.Scan(&bar, &level, &total); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Level = int32(level)
|
||||
|
||||
if c.Events.ExperienceChange != nil {
|
||||
return c.Events.ExperienceChange(float32(bar), int32(level), int32(total))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendPlayerPositionAndLookPacket(c *Client) error {
|
||||
return c.conn.WritePacket(ptypes.PositionAndLookServerbound{
|
||||
X: pk.Double(c.Pos.X),
|
||||
Y: pk.Double(c.Pos.Y),
|
||||
Z: pk.Double(c.Pos.Z),
|
||||
Yaw: pk.Float(c.Pos.Yaw),
|
||||
Pitch: pk.Float(c.Pos.Pitch),
|
||||
OnGround: pk.Boolean(c.Pos.OnGround),
|
||||
}.Encode())
|
||||
}
|
||||
|
||||
func sendPlayerPositionPacket(c *Client) error {
|
||||
return c.conn.WritePacket(ptypes.Position{
|
||||
X: pk.Double(c.Pos.X),
|
||||
Y: pk.Double(c.Pos.Y),
|
||||
Z: pk.Double(c.Pos.Z),
|
||||
OnGround: pk.Boolean(c.Pos.OnGround),
|
||||
}.Encode())
|
||||
}
|
||||
|
||||
func sendPlayerLookPacket(c *Client) error {
|
||||
return c.conn.WritePacket(ptypes.Look{
|
||||
Yaw: pk.Float(c.Pos.Yaw),
|
||||
Pitch: pk.Float(c.Pos.Pitch),
|
||||
OnGround: pk.Boolean(c.Pos.OnGround),
|
||||
}.Encode())
|
||||
}
|
||||
|
29
bot/login.go
29
bot/login.go
@ -25,34 +25,35 @@ type Auth struct {
|
||||
AsTk string
|
||||
}
|
||||
|
||||
// 加密请求
|
||||
func handleEncryptionRequest(c *Client, pack pk.Packet) error {
|
||||
//创建AES对称加密密钥
|
||||
func handleEncryptionRequest(c *Client, p pk.Packet) error {
|
||||
// 创建AES对称加密密钥
|
||||
key, encoStream, decoStream := newSymmetricEncryption()
|
||||
|
||||
//解析EncryptionRequest包
|
||||
// Read EncryptionRequest
|
||||
var er encryptionRequest
|
||||
if err := pack.Scan(&er); err != nil {
|
||||
if err := p.Scan(&er); err != nil {
|
||||
return err
|
||||
}
|
||||
err := loginAuth(c.AsTk, c.Name, c.Auth.UUID, key, er) //向Mojang验证
|
||||
|
||||
err := loginAuth(c.Auth, key, er) //向Mojang验证
|
||||
if err != nil {
|
||||
return fmt.Errorf("login fail: %v", err)
|
||||
}
|
||||
|
||||
// 响应加密请求
|
||||
var p pk.Packet // Encryption Key Response
|
||||
// Write Encryption Key Response
|
||||
p, err = genEncryptionKeyResponse(key, er.PublicKey, er.VerifyToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("gen encryption key response fail: %v", err)
|
||||
}
|
||||
err = c.conn.WritePacket(p)
|
||||
|
||||
err = c.Conn.WritePacket(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 设置连接加密
|
||||
c.conn.SetCipher(encoStream, decoStream)
|
||||
c.Conn.SetCipher(encoStream, decoStream)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -122,16 +123,16 @@ type request struct {
|
||||
ServerID string `json:"serverId"`
|
||||
}
|
||||
|
||||
func loginAuth(AsTk, name, UUID string, shareSecret []byte, er encryptionRequest) error {
|
||||
func loginAuth(auth Auth, shareSecret []byte, er encryptionRequest) error {
|
||||
digest := authDigest(er.ServerID, shareSecret, er.PublicKey)
|
||||
|
||||
client := http.Client{}
|
||||
requestPacket, err := json.Marshal(
|
||||
request{
|
||||
AccessToken: AsTk,
|
||||
AccessToken: auth.AsTk,
|
||||
SelectedProfile: profile{
|
||||
ID: UUID,
|
||||
Name: name,
|
||||
ID: auth.UUID,
|
||||
Name: auth.Name,
|
||||
},
|
||||
ServerID: digest,
|
||||
},
|
||||
@ -155,7 +156,7 @@ func loginAuth(AsTk, name, UUID string, shareSecret []byte, er encryptionRequest
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if resp.Status != "204 No Content" {
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
return fmt.Errorf("auth fail: %s", string(body))
|
||||
}
|
||||
return nil
|
||||
|
163
bot/mcbot.go
163
bot/mcbot.go
@ -6,10 +6,10 @@ package bot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
mcnet "github.com/Tnze/go-mc/net"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
@ -22,9 +22,15 @@ const DefaultPort = 25565
|
||||
// JoinServer connect a Minecraft server for playing the game.
|
||||
// Using roughly the same way to parse address as minecraft.
|
||||
func (c *Client) JoinServer(addr string) (err error) {
|
||||
return c.JoinServerWithDialer(&net.Dialer{}, addr)
|
||||
return c.join(&net.Dialer{}, addr)
|
||||
}
|
||||
|
||||
// JoinServerWithDialer is similar to JoinServer but using a Dialer.
|
||||
func (c *Client) JoinServerWithDialer(d *net.Dialer, addr string) (err error) {
|
||||
return c.join(d, addr)
|
||||
}
|
||||
|
||||
// parseAddress will lookup SRV records for the address
|
||||
func parseAddress(r *net.Resolver, addr string) (string, error) {
|
||||
const missingPort = "missing port in address"
|
||||
var port uint16
|
||||
@ -49,113 +55,110 @@ func parseAddress(r *net.Resolver, addr string) (string, error) {
|
||||
return net.JoinHostPort(host, strconv.FormatUint(uint64(port), 10)), nil
|
||||
}
|
||||
|
||||
// JoinServerWithDialer is similar to JoinServer but using a Dialer.
|
||||
func (c *Client) JoinServerWithDialer(d *net.Dialer, addr string) (err error) {
|
||||
addr, err = parseAddress(d.Resolver, addr)
|
||||
func (c *Client) join(d *net.Dialer, addr string) error {
|
||||
const Handshake = 0x00
|
||||
addrSrv, err := parseAddress(d.Resolver, addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse address error: %w", err)
|
||||
return LoginErr{"resolved address", err}
|
||||
}
|
||||
return c.join(d, addr)
|
||||
}
|
||||
|
||||
func (c *Client) join(d *net.Dialer, addr string) (err error) {
|
||||
conn, err := d.Dial("tcp", addr)
|
||||
// Split Host and Port
|
||||
host, portStr, err := net.SplitHostPort(addrSrv)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("bot: connect server fail: %v", err)
|
||||
return err
|
||||
}
|
||||
//Set Conn
|
||||
c.conn = mcnet.WrapConn(conn)
|
||||
|
||||
//Get Host and Port
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("bot: connect server fail: %v", err)
|
||||
return err
|
||||
return LoginErr{"split address", err}
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("bot: connect server fail: %v", err)
|
||||
return err
|
||||
return LoginErr{"parse port", err}
|
||||
}
|
||||
|
||||
//Handshake
|
||||
err = c.conn.WritePacket(
|
||||
//Handshake Packet
|
||||
pk.Marshal(
|
||||
0x00, //Handshake packet ID
|
||||
pk.VarInt(ProtocolVersion), //Protocol version
|
||||
pk.String(host), //Server's address
|
||||
pk.UnsignedShort(port),
|
||||
pk.Byte(2),
|
||||
))
|
||||
// Dial connection
|
||||
c.Conn, err = mcnet.DialMC(addrSrv)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("bot: send handshake packect fail: %v", err)
|
||||
return
|
||||
return LoginErr{"connect server", err}
|
||||
}
|
||||
|
||||
//Login
|
||||
err = c.conn.WritePacket(
|
||||
//LoginStart Packet
|
||||
pk.Marshal(0, pk.String(c.Name)))
|
||||
// Handshake
|
||||
err = c.Conn.WritePacket(pk.Marshal(
|
||||
Handshake,
|
||||
pk.VarInt(ProtocolVersion), // Protocol version
|
||||
pk.String(host), // Host
|
||||
pk.UnsignedShort(port), // Port
|
||||
pk.Byte(2),
|
||||
))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("bot: send login start packect fail: %v", err)
|
||||
return
|
||||
return LoginErr{"handshake", err}
|
||||
}
|
||||
|
||||
// Login Start
|
||||
err = c.Conn.WritePacket(pk.Marshal(
|
||||
packetid.LoginStart,
|
||||
pk.String(c.Auth.Name),
|
||||
))
|
||||
if err != nil {
|
||||
return LoginErr{"login start", err}
|
||||
}
|
||||
|
||||
for {
|
||||
//Recive Packet
|
||||
var pack pk.Packet
|
||||
if err = c.conn.ReadPacket(&pack); err != nil {
|
||||
err = fmt.Errorf("bot: recv packet for Login fail: %v", err)
|
||||
return
|
||||
//Receive Packet
|
||||
var p pk.Packet
|
||||
if err = c.Conn.ReadPacket(&p); err != nil {
|
||||
return LoginErr{"receive packet", err}
|
||||
}
|
||||
|
||||
//Handle Packet
|
||||
switch pack.ID {
|
||||
case 0x00: //Disconnect
|
||||
var reason pk.String
|
||||
err = pack.Scan(&reason)
|
||||
switch p.ID {
|
||||
case packetid.Disconnect: //Disconnect
|
||||
var reason chat.Message
|
||||
err = p.Scan(&reason)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("bot: read Disconnect message fail: %v", err)
|
||||
} else {
|
||||
err = fmt.Errorf("bot: connect disconnected by server: %s", reason)
|
||||
return LoginErr{"disconnect", err}
|
||||
}
|
||||
return
|
||||
case 0x01: //Encryption Request
|
||||
if err := handleEncryptionRequest(c, pack); err != nil {
|
||||
return fmt.Errorf("bot: encryption fail: %v", err)
|
||||
return LoginErr{"disconnect", DisconnectErr(reason)}
|
||||
|
||||
case packetid.EncryptionBeginClientbound: //Encryption Request
|
||||
if err := handleEncryptionRequest(c, p); err != nil {
|
||||
return LoginErr{"encryption", err}
|
||||
}
|
||||
|
||||
case packetid.Success: //Login Success
|
||||
err := p.Scan(
|
||||
(*pk.UUID)(&c.UUID),
|
||||
(*pk.String)(&c.Name),
|
||||
)
|
||||
if err != nil {
|
||||
return LoginErr{"login success", err}
|
||||
}
|
||||
case 0x02: //Login Success
|
||||
// uuid, l := pk.UnpackString(pack.Data)
|
||||
// name, _ := unpackString(pack.Data[l:])
|
||||
return nil
|
||||
case 0x03: //Set Compression
|
||||
|
||||
case packetid.Compress: //Set Compression
|
||||
var threshold pk.VarInt
|
||||
if err := pack.Scan(&threshold); err != nil {
|
||||
return fmt.Errorf("bot: set compression fail: %v", err)
|
||||
}
|
||||
c.conn.SetThreshold(int(threshold))
|
||||
case 0x04: //Login Plugin Request
|
||||
if err := handlePluginPacket(c, pack); err != nil {
|
||||
return fmt.Errorf("bot: handle plugin packet fail: %v", err)
|
||||
if err := p.Scan(&threshold); err != nil {
|
||||
return LoginErr{"compression", err}
|
||||
}
|
||||
c.Conn.SetThreshold(int(threshold))
|
||||
|
||||
case packetid.LoginPluginRequest: //Login Plugin Request
|
||||
// TODO: Handle login plugin request
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Conn return the MCConn of the Client.
|
||||
// Only used when you want to handle the packets by yourself
|
||||
func (c *Client) Conn() *mcnet.Conn {
|
||||
return c.conn
|
||||
type LoginErr struct {
|
||||
Stage string
|
||||
Err error
|
||||
}
|
||||
|
||||
// SendMessage sends a chat message.
|
||||
func (c *Client) SendMessage(msg string) error {
|
||||
return c.conn.WritePacket(
|
||||
pk.Marshal(
|
||||
packetid.ChatServerbound,
|
||||
pk.String(msg),
|
||||
),
|
||||
)
|
||||
func (l LoginErr) Error() string {
|
||||
return "bot: " + l.Stage + " error: " + l.Err.Error()
|
||||
}
|
||||
|
||||
func (l LoginErr) Unwrap() error {
|
||||
return l.Err
|
||||
}
|
||||
|
||||
type DisconnectErr chat.Message
|
||||
|
||||
func (d DisconnectErr) Error() string {
|
||||
return "disconnect because: " + chat.Message(d).String()
|
||||
}
|
||||
|
190
bot/motion.go
190
bot/motion.go
@ -1,190 +0,0 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/Tnze/go-mc/net/ptypes"
|
||||
)
|
||||
|
||||
// SwingArm swing player's arm.
|
||||
// hand could be one of 0: main hand, 1: off hand.
|
||||
// It's just animation.
|
||||
func (c *Client) SwingArm(hand int) error {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
packetid.ArmAnimation,
|
||||
pk.VarInt(hand),
|
||||
))
|
||||
}
|
||||
|
||||
// Respawn the player when it was dead.
|
||||
func (c *Client) Respawn() error {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
packetid.ClientCommand,
|
||||
pk.VarInt(0),
|
||||
))
|
||||
}
|
||||
|
||||
// UseItem use the item player handing.
|
||||
// hand could be one of 0: main hand, 1: off hand
|
||||
func (c *Client) UseItem(hand int) error {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
packetid.UseItem,
|
||||
pk.VarInt(hand),
|
||||
))
|
||||
}
|
||||
|
||||
// UseEntity used by player to right-clicks another entity.
|
||||
// hand could be one of 0: main hand, 1: off hand.
|
||||
// A Notchian server only accepts this packet if
|
||||
// the entity being attacked/used is visible without obstruction
|
||||
// and within a 4-unit radius of the player's position.
|
||||
func (c *Client) UseEntity(entityID int32, hand int) error {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
packetid.UseEntity,
|
||||
pk.VarInt(entityID),
|
||||
pk.VarInt(0),
|
||||
pk.VarInt(hand),
|
||||
))
|
||||
}
|
||||
|
||||
// AttackEntity used by player to left-clicks another entity.
|
||||
// The attack version of UseEntity. Has the same limit.
|
||||
func (c *Client) AttackEntity(entityID int32, hand int) error {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
packetid.UseEntity,
|
||||
pk.VarInt(entityID),
|
||||
pk.VarInt(1),
|
||||
))
|
||||
}
|
||||
|
||||
// UseEntityAt is a variety of UseEntity with target location
|
||||
func (c *Client) UseEntityAt(entityID int32, x, y, z float32, hand int) error {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
packetid.UseEntity,
|
||||
pk.VarInt(entityID),
|
||||
pk.VarInt(2),
|
||||
pk.Float(x), pk.Float(y), pk.Float(z),
|
||||
pk.VarInt(hand),
|
||||
))
|
||||
}
|
||||
|
||||
// Chat send chat as chat message or command at textbox.
|
||||
func (c *Client) Chat(msg string) error {
|
||||
if len(msg) > 256 {
|
||||
return errors.New("message too long")
|
||||
}
|
||||
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
packetid.ChatServerbound,
|
||||
pk.String(msg),
|
||||
))
|
||||
}
|
||||
|
||||
// PluginMessage is used by mods and plugins to send their data.
|
||||
func (c *Client) PluginMessage(channel string, msg []byte) error {
|
||||
return c.conn.WritePacket((&ptypes.PluginMessage{
|
||||
Channel: pk.Identifier(channel),
|
||||
Data: ptypes.PluginData(msg),
|
||||
}).Encode())
|
||||
}
|
||||
|
||||
// UseBlock is used to place or use a block.
|
||||
// hand is the hand from which the block is placed; 0: main hand, 1: off hand.
|
||||
// face is the face on which the block is placed.
|
||||
//
|
||||
// Cursor position is the position of the crosshair on the block:
|
||||
// cursorX, from 0 to 1 increasing from west to east;
|
||||
// cursorY, from 0 to 1 increasing from bottom to top;
|
||||
// cursorZ, from 0 to 1 increasing from north to south.
|
||||
//
|
||||
// insideBlock is true when the player's head is inside of a block's collision.
|
||||
func (c *Client) UseBlock(hand, locX, locY, locZ, face int, cursorX, cursorY, cursorZ float32, insideBlock bool) error {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
packetid.UseItem,
|
||||
pk.VarInt(hand),
|
||||
pk.Position{X: locX, Y: locY, Z: locZ},
|
||||
pk.VarInt(face),
|
||||
pk.Float(cursorX), pk.Float(cursorY), pk.Float(cursorZ),
|
||||
pk.Boolean(insideBlock),
|
||||
))
|
||||
}
|
||||
|
||||
// SelectItem used to change the slot selection in hotbar.
|
||||
// slot should from 0 to 8
|
||||
func (c *Client) SelectItem(slot int) error {
|
||||
if slot < 0 || slot > 8 {
|
||||
return errors.New("invalid slot: " + strconv.Itoa(slot))
|
||||
}
|
||||
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
packetid.HeldItemSlotServerbound,
|
||||
pk.Short(slot),
|
||||
))
|
||||
}
|
||||
|
||||
// PickItem used to swap out an empty space on the hotbar with the item in the given inventory slot.
|
||||
// The Notchain client uses this for pick block functionality (middle click) to retrieve items from the inventory.
|
||||
//
|
||||
// The server will first search the player's hotbar for an empty slot,
|
||||
// starting from the current slot and looping around to the slot before it.
|
||||
// If there are no empty slots, it will start a second search from the
|
||||
// current slot and find the first slot that does not contain an enchanted item.
|
||||
// If there still are no slots that meet that criteria, then the server will
|
||||
// use the currently selected slot. After finding the appropriate slot,
|
||||
// the server swaps the items and then change player's selected slot (cause the HeldItemChange event).
|
||||
func (c *Client) PickItem(slot int) error {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
packetid.PickItem,
|
||||
pk.VarInt(slot),
|
||||
))
|
||||
}
|
||||
|
||||
func (c *Client) playerAction(status, locX, locY, locZ, face int) error {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
packetid.BlockDig,
|
||||
pk.VarInt(status),
|
||||
pk.Position{X: locX, Y: locY, Z: locZ},
|
||||
pk.Byte(face),
|
||||
))
|
||||
}
|
||||
|
||||
// Dig used to start, end or cancel a digging
|
||||
// status is 0 for start digging, 1 for cancel and 2 if client think it done.
|
||||
// To digging a block without cancel, use status 0 and 2 once each.
|
||||
func (c *Client) Dig(status, locX, locY, locZ, face int) error {
|
||||
return c.playerAction(status, locX, locY, locZ, face)
|
||||
}
|
||||
|
||||
// DropItemStack drop the entire selected stack
|
||||
func (c *Client) DropItemStack() error {
|
||||
return c.playerAction(3, 0, 0, 0, 0)
|
||||
}
|
||||
|
||||
// DropItem drop one item in selected stack
|
||||
func (c *Client) DropItem() error {
|
||||
return c.playerAction(4, 0, 0, 0, 0)
|
||||
}
|
||||
|
||||
// UseItemEnd used to finish UseItem, like eating food, pulling back bows.
|
||||
func (c *Client) UseItemEnd() error {
|
||||
return c.playerAction(5, 0, 0, 0, 0)
|
||||
}
|
||||
|
||||
// SwapItem used to swap the items in hands.
|
||||
func (c *Client) SwapItem() error {
|
||||
return c.playerAction(6, 0, 0, 0, 0)
|
||||
}
|
||||
|
||||
// Disconnect disconnect the server.
|
||||
// Server will close the connection.
|
||||
func (c *Client) disconnect() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// SendPacket send the packet to server.
|
||||
func (c *Client) SendPacket(packet pk.Packet) error {
|
||||
return c.conn.WritePacket(packet)
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"github.com/Tnze/go-mc/bot/world"
|
||||
"github.com/Tnze/go-mc/data/block"
|
||||
)
|
||||
|
||||
var (
|
||||
safeStepBlocks = make(map[world.BlockStatus]struct{}, 1024)
|
||||
stepBlocks = []block.Block{
|
||||
block.Stone,
|
||||
block.Granite,
|
||||
block.PolishedGranite,
|
||||
block.Diorite,
|
||||
block.PolishedDiorite,
|
||||
block.Andesite,
|
||||
block.PolishedAndesite,
|
||||
block.GrassBlock,
|
||||
block.GrassPath,
|
||||
block.Dirt,
|
||||
block.CoarseDirt,
|
||||
block.Cobblestone,
|
||||
block.OakPlanks,
|
||||
block.SprucePlanks,
|
||||
block.BirchPlanks,
|
||||
block.JunglePlanks,
|
||||
block.AcaciaPlanks,
|
||||
block.DarkOakPlanks,
|
||||
block.Bedrock,
|
||||
block.GoldOre,
|
||||
block.IronOre,
|
||||
block.CoalOre,
|
||||
block.Glass,
|
||||
block.LapisOre,
|
||||
block.Sandstone,
|
||||
block.RedstoneOre,
|
||||
block.OakStairs,
|
||||
block.AcaciaStairs,
|
||||
block.DarkOakStairs,
|
||||
block.RedSandstoneStairs,
|
||||
block.PolishedGraniteStairs,
|
||||
block.SmoothRedSandstoneStairs,
|
||||
block.MossyStoneBrickStairs,
|
||||
block.PolishedDioriteStairs,
|
||||
block.MossyCobblestoneStairs,
|
||||
block.EndStoneBrickStairs,
|
||||
block.StoneStairs,
|
||||
block.SmoothSandstoneStairs,
|
||||
block.SmoothQuartzStairs,
|
||||
block.GraniteStairs,
|
||||
block.AndesiteStairs,
|
||||
block.RedNetherBrickStairs,
|
||||
block.PolishedAndesiteStairs,
|
||||
block.OakSlab,
|
||||
block.AcaciaSlab,
|
||||
block.DarkOakSlab,
|
||||
block.RedSandstoneSlab,
|
||||
block.PolishedGraniteSlab,
|
||||
block.SmoothRedSandstoneSlab,
|
||||
block.MossyStoneBrickSlab,
|
||||
block.PolishedDioriteSlab,
|
||||
block.MossyCobblestoneSlab,
|
||||
block.EndStoneBrickSlab,
|
||||
block.StoneSlab,
|
||||
block.SmoothSandstoneSlab,
|
||||
block.SmoothQuartzSlab,
|
||||
block.GraniteSlab,
|
||||
block.AndesiteSlab,
|
||||
block.RedNetherBrickSlab,
|
||||
block.PolishedAndesiteSlab,
|
||||
}
|
||||
|
||||
slabs = map[block.ID]struct{}{
|
||||
block.OakSlab.ID: struct{}{},
|
||||
block.AcaciaSlab.ID: struct{}{},
|
||||
block.DarkOakSlab.ID: struct{}{},
|
||||
block.RedSandstoneSlab.ID: struct{}{},
|
||||
block.PolishedGraniteSlab.ID: struct{}{},
|
||||
block.SmoothRedSandstoneSlab.ID: struct{}{},
|
||||
block.MossyStoneBrickSlab.ID: struct{}{},
|
||||
block.PolishedDioriteSlab.ID: struct{}{},
|
||||
block.MossyCobblestoneSlab.ID: struct{}{},
|
||||
block.EndStoneBrickSlab.ID: struct{}{},
|
||||
block.StoneSlab.ID: struct{}{},
|
||||
block.SmoothSandstoneSlab.ID: struct{}{},
|
||||
block.SmoothQuartzSlab.ID: struct{}{},
|
||||
block.GraniteSlab.ID: struct{}{},
|
||||
block.AndesiteSlab.ID: struct{}{},
|
||||
block.RedNetherBrickSlab.ID: struct{}{},
|
||||
block.PolishedAndesiteSlab.ID: struct{}{},
|
||||
}
|
||||
stairs = map[block.ID]struct{}{
|
||||
block.OakStairs.ID: struct{}{},
|
||||
block.AcaciaStairs.ID: struct{}{},
|
||||
block.DarkOakStairs.ID: struct{}{},
|
||||
block.RedSandstoneStairs.ID: struct{}{},
|
||||
block.PolishedGraniteStairs.ID: struct{}{},
|
||||
block.SmoothRedSandstoneStairs.ID: struct{}{},
|
||||
block.MossyStoneBrickStairs.ID: struct{}{},
|
||||
block.PolishedDioriteStairs.ID: struct{}{},
|
||||
block.MossyCobblestoneStairs.ID: struct{}{},
|
||||
block.EndStoneBrickStairs.ID: struct{}{},
|
||||
block.StoneStairs.ID: struct{}{},
|
||||
block.SmoothSandstoneStairs.ID: struct{}{},
|
||||
block.SmoothQuartzStairs.ID: struct{}{},
|
||||
block.GraniteStairs.ID: struct{}{},
|
||||
block.AndesiteStairs.ID: struct{}{},
|
||||
block.RedNetherBrickStairs.ID: struct{}{},
|
||||
block.PolishedAndesiteStairs.ID: struct{}{},
|
||||
}
|
||||
|
||||
safeWalkBlocks = make(map[world.BlockStatus]struct{}, 128)
|
||||
walkBlocks = []block.Block{
|
||||
block.Air,
|
||||
block.CaveAir,
|
||||
block.Grass,
|
||||
block.Torch,
|
||||
block.OakSign,
|
||||
block.SpruceSign,
|
||||
block.BirchSign,
|
||||
block.AcaciaSign,
|
||||
block.JungleSign,
|
||||
block.DarkOakSign,
|
||||
block.OakWallSign,
|
||||
block.SpruceWallSign,
|
||||
block.BirchWallSign,
|
||||
block.AcaciaWallSign,
|
||||
block.JungleWallSign,
|
||||
block.DarkOakWallSign,
|
||||
block.Cornflower,
|
||||
block.TallGrass,
|
||||
}
|
||||
|
||||
additionalCostBlocks = map[*block.Block]int{
|
||||
&block.Rail: 120,
|
||||
&block.PoweredRail: 200,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
for _, b := range stepBlocks {
|
||||
if b.MinStateID == b.MaxStateID {
|
||||
safeStepBlocks[world.BlockStatus(b.MinStateID)] = struct{}{}
|
||||
} else {
|
||||
for i := b.MinStateID; i <= b.MaxStateID; i++ {
|
||||
safeStepBlocks[world.BlockStatus(i)] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, b := range walkBlocks {
|
||||
if b.MinStateID == b.MaxStateID {
|
||||
safeWalkBlocks[world.BlockStatus(b.MinStateID)] = struct{}{}
|
||||
} else {
|
||||
for i := b.MinStateID; i <= b.MaxStateID; i++ {
|
||||
safeWalkBlocks[world.BlockStatus(i)] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SteppableBlock(bID world.BlockStatus) bool {
|
||||
_, ok := safeStepBlocks[bID]
|
||||
return ok
|
||||
}
|
||||
|
||||
func AirLikeBlock(bID world.BlockStatus) bool {
|
||||
_, ok := safeWalkBlocks[bID]
|
||||
return ok
|
||||
}
|
||||
|
||||
func IsLadder(bID world.BlockStatus) bool {
|
||||
return uint32(bID) >= block.Ladder.MinStateID && uint32(bID) <= block.Ladder.MaxStateID
|
||||
}
|
||||
|
||||
func IsSlab(bID world.BlockStatus) bool {
|
||||
_, isSlab := slabs[block.StateID[uint32(bID)]]
|
||||
return isSlab
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package path
|
||||
|
||||
// Inputs describes the desired movements of the player.
|
||||
type Inputs struct {
|
||||
Yaw, Pitch float64
|
||||
|
||||
ThrottleX, ThrottleZ float64
|
||||
|
||||
Jump bool
|
||||
}
|
@ -1,362 +0,0 @@
|
||||
package path
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tnze/go-mc/bot/world"
|
||||
"github.com/Tnze/go-mc/data/block"
|
||||
)
|
||||
|
||||
// Cardinal directions.
|
||||
type Direction uint8
|
||||
|
||||
func (d Direction) Offset() (x, y, z int) {
|
||||
switch d {
|
||||
case North:
|
||||
return 0, 0, -1
|
||||
case South:
|
||||
return 0, 0, 1
|
||||
case East:
|
||||
return 1, 0, 0
|
||||
case West:
|
||||
return -1, 0, 0
|
||||
}
|
||||
panic(fmt.Sprintf("unknown direction value: %v", d))
|
||||
}
|
||||
|
||||
func (d Direction) Offset2x() (x, y, z int) {
|
||||
x, y, z = d.Offset()
|
||||
return x + x, y + y, z + z
|
||||
}
|
||||
|
||||
func (d Direction) String() string {
|
||||
switch d {
|
||||
case North:
|
||||
return "north"
|
||||
case South:
|
||||
return "south"
|
||||
case East:
|
||||
return "east"
|
||||
case West:
|
||||
return "west"
|
||||
}
|
||||
return fmt.Sprintf("Direction?<%d>", int(d))
|
||||
}
|
||||
|
||||
// Valid direction values.
|
||||
const (
|
||||
North Direction = iota
|
||||
South
|
||||
West
|
||||
East
|
||||
)
|
||||
|
||||
func LadderDirection(bStateID world.BlockStatus) Direction {
|
||||
return Direction(((uint32(bStateID) - block.Ladder.MinStateID) & 0xE) >> 1)
|
||||
}
|
||||
|
||||
func ChestDirection(bStateID world.BlockStatus) Direction {
|
||||
return Direction(((uint32(bStateID) - block.Chest.MinStateID) / 6) & 0x3)
|
||||
}
|
||||
|
||||
func StairsDirection(bStateID world.BlockStatus) Direction {
|
||||
b := block.StateID[uint32(bStateID)]
|
||||
return Direction(((uint32(bStateID) - block.ByID[b].MinStateID) / 20) & 0x3)
|
||||
}
|
||||
|
||||
func SlabIsBottom(bStateID world.BlockStatus) bool {
|
||||
b := block.StateID[uint32(bStateID)]
|
||||
return ((uint32(bStateID)-block.ByID[b].MinStateID)&0xE)>>1 == 1
|
||||
}
|
||||
|
||||
// Movement represents a single type of movement in a path.
|
||||
type Movement uint8
|
||||
|
||||
var allMovements = []Movement{TraverseNorth, TraverseSouth, TraverseEast, TraverseWest,
|
||||
TraverseNorthWest, TraverseNorthEast, TraverseSouthWest, TraverseSouthEast,
|
||||
DropNorth, DropSouth, DropEast, DropWest,
|
||||
Drop2North, Drop2South, Drop2East, Drop2West,
|
||||
AscendNorth, AscendSouth, AscendEast, AscendWest,
|
||||
DescendLadder, DescendLadderNorth, DescendLadderSouth, DescendLadderEast, DescendLadderWest,
|
||||
AscendLadder,
|
||||
JumpCrossNorth, JumpCrossSouth, JumpCrossEast, JumpCrossWest,
|
||||
}
|
||||
|
||||
// Valid movement values.
|
||||
const (
|
||||
Waypoint Movement = iota
|
||||
TraverseNorth
|
||||
TraverseSouth
|
||||
TraverseEast
|
||||
TraverseWest
|
||||
TraverseNorthEast
|
||||
TraverseNorthWest
|
||||
TraverseSouthEast
|
||||
TraverseSouthWest
|
||||
DropNorth
|
||||
DropSouth
|
||||
DropEast
|
||||
DropWest
|
||||
Drop2North
|
||||
Drop2South
|
||||
Drop2East
|
||||
Drop2West
|
||||
AscendNorth
|
||||
AscendSouth
|
||||
AscendEast
|
||||
AscendWest
|
||||
DescendLadder
|
||||
DescendLadderNorth
|
||||
DescendLadderSouth
|
||||
DescendLadderEast
|
||||
DescendLadderWest
|
||||
AscendLadder
|
||||
JumpCrossEast
|
||||
JumpCrossWest
|
||||
JumpCrossNorth
|
||||
JumpCrossSouth
|
||||
)
|
||||
|
||||
func (m Movement) Possible(nav *Nav, x, y, z int, from V3, previous Movement) bool {
|
||||
switch m {
|
||||
case Waypoint, TraverseNorth, TraverseSouth, TraverseEast, TraverseWest:
|
||||
if !SteppableBlock(nav.World.GetBlockStatus(x, y, z)) {
|
||||
return false
|
||||
}
|
||||
b1, b2 := nav.World.GetBlockStatus(x, y+1, z), nav.World.GetBlockStatus(x, y+2, z)
|
||||
u1, u2 := AirLikeBlock(b1) || IsLadder(b1), AirLikeBlock(b2) || IsLadder(b2)
|
||||
return u1 && u2
|
||||
|
||||
case TraverseNorthWest, TraverseNorthEast, TraverseSouthWest, TraverseSouthEast:
|
||||
if !SteppableBlock(nav.World.GetBlockStatus(x, y, z)) {
|
||||
return false
|
||||
}
|
||||
if !AirLikeBlock(nav.World.GetBlockStatus(x, y+1, z)) || !AirLikeBlock(nav.World.GetBlockStatus(x, y+2, z)) {
|
||||
return false
|
||||
}
|
||||
if !AirLikeBlock(nav.World.GetBlockStatus(from.X, y+1, z)) || !AirLikeBlock(nav.World.GetBlockStatus(from.X, y+2, z)) {
|
||||
return false
|
||||
}
|
||||
return AirLikeBlock(nav.World.GetBlockStatus(x, y+1, from.Z)) && AirLikeBlock(nav.World.GetBlockStatus(x, y+2, from.Z))
|
||||
|
||||
case Drop2North, Drop2South, Drop2East, Drop2West:
|
||||
if !AirLikeBlock(nav.World.GetBlockStatus(x, y+4, z)) {
|
||||
return false
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case DropNorth, DropSouth, DropEast, DropWest:
|
||||
for amt := 0; amt < 3; amt++ {
|
||||
if !AirLikeBlock(nav.World.GetBlockStatus(x, y+amt+1, z)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return SteppableBlock(nav.World.GetBlockStatus(x, y, z))
|
||||
|
||||
case AscendNorth, AscendSouth, AscendEast, AscendWest:
|
||||
if !AirLikeBlock(nav.World.GetBlockStatus(x, y+1, z)) || !AirLikeBlock(nav.World.GetBlockStatus(x, y+2, z)) {
|
||||
return false
|
||||
}
|
||||
return SteppableBlock(nav.World.GetBlockStatus(x, y, z)) &&
|
||||
AirLikeBlock(nav.World.GetBlockStatus(from.X, from.Y+1, from.Z)) &&
|
||||
AirLikeBlock(nav.World.GetBlockStatus(from.X, from.Y+2, from.Z))
|
||||
|
||||
case DescendLadder, AscendLadder:
|
||||
if bID := nav.World.GetBlockStatus(x, y+1, z); !AirLikeBlock(bID) && !IsLadder(bID) {
|
||||
return false
|
||||
}
|
||||
return IsLadder(nav.World.GetBlockStatus(x, y, z))
|
||||
|
||||
case DescendLadderNorth, DescendLadderSouth, DescendLadderEast, DescendLadderWest:
|
||||
for amt := 0; amt < 2; amt++ {
|
||||
if bID := nav.World.GetBlockStatus(x, y+amt+1, z); !AirLikeBlock(bID) && !IsLadder(bID) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return IsLadder(nav.World.GetBlockStatus(x, y, z))
|
||||
|
||||
case JumpCrossEast, JumpCrossWest, JumpCrossNorth, JumpCrossSouth:
|
||||
if !AirLikeBlock(nav.World.GetBlockStatus(x, y+1, z)) || !AirLikeBlock(nav.World.GetBlockStatus(x, y+2, z)) || !AirLikeBlock(nav.World.GetBlockStatus(x, y+3, z)) {
|
||||
return false
|
||||
}
|
||||
midX, midZ := (from.X+x)/2, (from.Z+z)/2
|
||||
if !AirLikeBlock(nav.World.GetBlockStatus(midX, y+1, midZ)) || !AirLikeBlock(nav.World.GetBlockStatus(midX, y+2, midZ)) || !AirLikeBlock(nav.World.GetBlockStatus(midX, y+3, midZ)) {
|
||||
return false
|
||||
}
|
||||
if !AirLikeBlock(nav.World.GetBlockStatus(from.X, from.Y+3, from.Z)) {
|
||||
return false
|
||||
}
|
||||
return SteppableBlock(nav.World.GetBlockStatus(x, y, z))
|
||||
|
||||
default:
|
||||
panic(m)
|
||||
}
|
||||
}
|
||||
|
||||
func (m Movement) Offset() (x, y, z int) {
|
||||
switch m {
|
||||
case Waypoint:
|
||||
return 0, 0, 0
|
||||
case TraverseNorth:
|
||||
return North.Offset()
|
||||
case TraverseSouth:
|
||||
return South.Offset()
|
||||
case TraverseEast:
|
||||
return East.Offset()
|
||||
case TraverseWest:
|
||||
return West.Offset()
|
||||
|
||||
case JumpCrossEast:
|
||||
return East.Offset2x()
|
||||
case JumpCrossWest:
|
||||
return West.Offset2x()
|
||||
case JumpCrossNorth:
|
||||
return North.Offset2x()
|
||||
case JumpCrossSouth:
|
||||
return South.Offset2x()
|
||||
|
||||
case AscendLadder:
|
||||
return 0, 1, 0
|
||||
case DescendLadder:
|
||||
return 0, -1, 0
|
||||
case DropNorth, DescendLadderNorth:
|
||||
return 0, -1, -1
|
||||
case DropSouth, DescendLadderSouth:
|
||||
return 0, -1, 1
|
||||
case DropEast, DescendLadderEast:
|
||||
return 1, -1, 0
|
||||
case DropWest, DescendLadderWest:
|
||||
return -1, -1, 0
|
||||
case AscendNorth:
|
||||
return 0, 1, -1
|
||||
case AscendSouth:
|
||||
return 0, 1, 1
|
||||
case AscendEast:
|
||||
return 1, 1, 0
|
||||
case AscendWest:
|
||||
return -1, 1, 0
|
||||
|
||||
case TraverseNorthWest:
|
||||
return -1, 0, -1
|
||||
case TraverseNorthEast:
|
||||
return 1, 0, -1
|
||||
case TraverseSouthWest:
|
||||
return -1, 0, 1
|
||||
case TraverseSouthEast:
|
||||
return 1, 0, 1
|
||||
|
||||
case Drop2North:
|
||||
return 0, -2, -1
|
||||
case Drop2South:
|
||||
return 0, -2, 1
|
||||
case Drop2East:
|
||||
return 1, -2, 0
|
||||
case Drop2West:
|
||||
return -1, -2, 0
|
||||
|
||||
default:
|
||||
panic(m)
|
||||
}
|
||||
}
|
||||
|
||||
func (m Movement) BaseCost() float64 {
|
||||
switch m {
|
||||
case Waypoint:
|
||||
return 0
|
||||
case TraverseNorth, TraverseSouth, TraverseEast, TraverseWest:
|
||||
return 2
|
||||
case TraverseNorthWest, TraverseNorthEast, TraverseSouthWest, TraverseSouthEast:
|
||||
return 2.5
|
||||
|
||||
case DropNorth, DropSouth, DropEast, DropWest, Drop2North, Drop2South, Drop2East, Drop2West:
|
||||
return 4
|
||||
case AscendNorth, AscendSouth, AscendEast, AscendWest:
|
||||
return 4
|
||||
case DescendLadderNorth, DescendLadderSouth, DescendLadderEast, DescendLadderWest:
|
||||
return 1.5
|
||||
case DescendLadder:
|
||||
return 1
|
||||
case AscendLadder:
|
||||
return 3
|
||||
|
||||
case JumpCrossEast, JumpCrossWest, JumpCrossNorth, JumpCrossSouth:
|
||||
return 5.5
|
||||
default:
|
||||
panic(m)
|
||||
}
|
||||
}
|
||||
|
||||
func (m Movement) String() string {
|
||||
switch m {
|
||||
case Waypoint:
|
||||
return "waypoint"
|
||||
case TraverseNorth:
|
||||
return "traverse-north"
|
||||
case TraverseSouth:
|
||||
return "traverse-south"
|
||||
case TraverseEast:
|
||||
return "traverse-east"
|
||||
case TraverseWest:
|
||||
return "traverse-west"
|
||||
|
||||
case DropNorth:
|
||||
return "drop-north"
|
||||
case DropSouth:
|
||||
return "drop-south"
|
||||
case DropEast:
|
||||
return "drop-east"
|
||||
case DropWest:
|
||||
return "drop-west"
|
||||
case Drop2North:
|
||||
return "drop-2-north"
|
||||
case Drop2South:
|
||||
return "drop-2-south"
|
||||
case Drop2East:
|
||||
return "drop-2-east"
|
||||
case Drop2West:
|
||||
return "drop-2-west"
|
||||
|
||||
case AscendNorth:
|
||||
return "jump-north"
|
||||
case AscendSouth:
|
||||
return "jump-south"
|
||||
case AscendEast:
|
||||
return "jump-east"
|
||||
case AscendWest:
|
||||
return "jump-west"
|
||||
|
||||
case TraverseNorthWest:
|
||||
return "traverse-northwest"
|
||||
case TraverseNorthEast:
|
||||
return "traverse-northeast"
|
||||
case TraverseSouthWest:
|
||||
return "traverse-southwest"
|
||||
case TraverseSouthEast:
|
||||
return "traverse-southeast"
|
||||
|
||||
case DescendLadder:
|
||||
return "descend-ladder"
|
||||
case DescendLadderNorth:
|
||||
return "descend-ladder-north"
|
||||
case DescendLadderSouth:
|
||||
return "descend-ladder-south"
|
||||
case DescendLadderEast:
|
||||
return "descend-ladder-east"
|
||||
case DescendLadderWest:
|
||||
return "descend-ladder-west"
|
||||
|
||||
case AscendLadder:
|
||||
return "ascend-ladder"
|
||||
|
||||
case JumpCrossEast:
|
||||
return "jump-crossing-east"
|
||||
case JumpCrossWest:
|
||||
return "jump-crossing-west"
|
||||
case JumpCrossNorth:
|
||||
return "jump-crossing-north"
|
||||
case JumpCrossSouth:
|
||||
return "jump-crossing-south"
|
||||
default:
|
||||
panic(m)
|
||||
}
|
||||
}
|
208
bot/path/path.go
208
bot/path/path.go
@ -1,208 +0,0 @@
|
||||
// Package path implements pathfinding.
|
||||
package path
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/Tnze/go-mc/bot/world"
|
||||
"github.com/Tnze/go-mc/data/block"
|
||||
"github.com/beefsack/go-astar"
|
||||
)
|
||||
|
||||
// Point represents a point in 3D space.
|
||||
type Point struct {
|
||||
X, Y, Z float64
|
||||
}
|
||||
|
||||
type V3 struct {
|
||||
X, Y, Z int
|
||||
}
|
||||
|
||||
func (v V3) Cost(other V3) float64 {
|
||||
x, y, z := v.X-other.X, v.Y-other.Y, v.Z-other.Z
|
||||
return math.Sqrt(float64(x*x+z*z)) + math.Sqrt(1.2*float64(y*y))
|
||||
}
|
||||
|
||||
// Nav represents a navigation to a position.
|
||||
type Nav struct {
|
||||
World *world.World
|
||||
Start, Dest V3
|
||||
}
|
||||
|
||||
func (n *Nav) Path() (path []astar.Pather, distance float64, found bool) {
|
||||
return astar.Path(
|
||||
Tile{ // Start point
|
||||
Nav: n,
|
||||
Movement: Waypoint,
|
||||
Pos: n.Start,
|
||||
},
|
||||
Tile{ // Destination point
|
||||
Nav: n,
|
||||
Movement: Waypoint,
|
||||
Pos: n.Dest,
|
||||
})
|
||||
}
|
||||
|
||||
// Tile represents a point in a path. All tiles in a path are adjaceent their
|
||||
// preceeding tiles.
|
||||
type Tile struct {
|
||||
Nav *Nav
|
||||
|
||||
HalfBlock bool
|
||||
Movement Movement
|
||||
Pos V3
|
||||
BlockStatus world.BlockStatus
|
||||
ExtraCost int
|
||||
}
|
||||
|
||||
func (t Tile) PathNeighborCost(to astar.Pather) float64 {
|
||||
other := to.(Tile)
|
||||
return 1 + other.Movement.BaseCost()
|
||||
}
|
||||
|
||||
func (t Tile) PathEstimatedCost(to astar.Pather) float64 {
|
||||
other := to.(Tile)
|
||||
cost := t.Pos.Cost(other.Pos)
|
||||
|
||||
return cost + other.Movement.BaseCost()
|
||||
}
|
||||
|
||||
func (t Tile) PathNeighbors() []astar.Pather {
|
||||
possibles := make([]astar.Pather, 0, 8)
|
||||
|
||||
if c := t.PathEstimatedCost(Tile{Pos: t.Nav.Start}); c > 8000 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if t.Pos == t.Nav.Dest && t.Movement != Waypoint {
|
||||
dupe := t
|
||||
dupe.Movement = Waypoint
|
||||
dupe.BlockStatus = 0
|
||||
return []astar.Pather{dupe}
|
||||
}
|
||||
|
||||
for _, m := range allMovements {
|
||||
x, y, z := m.Offset()
|
||||
pos := V3{X: t.Pos.X + x, Y: t.Pos.Y + y, Z: t.Pos.Z + z}
|
||||
possible := m.Possible(t.Nav, pos.X, pos.Y, pos.Z, t.Pos, t.Movement)
|
||||
if possible {
|
||||
bStateID := t.Nav.World.GetBlockStatus(pos.X, pos.Y, pos.Z)
|
||||
possibles = append(possibles, Tile{
|
||||
Nav: t.Nav,
|
||||
Movement: m,
|
||||
Pos: pos,
|
||||
BlockStatus: bStateID,
|
||||
HalfBlock: IsSlab(bStateID) && SlabIsBottom(bStateID),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return possibles
|
||||
}
|
||||
|
||||
func (t Tile) Inputs(pos, deltaPos, vel Point, runTime time.Duration) Inputs {
|
||||
// Sufficient for simple movements.
|
||||
at := math.Atan2(-deltaPos.X, -deltaPos.Z)
|
||||
mdX, _, mdZ := t.Movement.Offset()
|
||||
wantYaw := -math.Atan2(float64(mdX), float64(mdZ)) * 180 / math.Pi
|
||||
out := Inputs{
|
||||
ThrottleX: math.Sin(at),
|
||||
ThrottleZ: math.Cos(at),
|
||||
Yaw: wantYaw,
|
||||
}
|
||||
if mdX == 0 && mdZ == 0 {
|
||||
out.Yaw = math.NaN()
|
||||
}
|
||||
if (rand.Int() % 14) == 0 {
|
||||
out.Pitch = float64((rand.Int() % 4) - 2)
|
||||
}
|
||||
|
||||
switch t.Movement {
|
||||
case DescendLadder, DescendLadderEast, DescendLadderWest, DescendLadderNorth, DescendLadderSouth:
|
||||
// Deadzone the throttle to prevent an accidental ascend.
|
||||
if dist2 := math.Sqrt(deltaPos.X*deltaPos.X + deltaPos.Z*deltaPos.Z); dist2 < (0.22 * 0.22 * 2) {
|
||||
out.ThrottleX, out.ThrottleZ = 0, 0
|
||||
}
|
||||
|
||||
case AscendLadder:
|
||||
dist2 := math.Sqrt(deltaPos.X*deltaPos.X + deltaPos.Z*deltaPos.Z)
|
||||
|
||||
if x, _, z := LadderDirection(t.BlockStatus).Offset(); dist2 > (0.8*0.8) && deltaPos.Y < 0 {
|
||||
pos.X -= (0.25 * float64(x))
|
||||
pos.Z -= (0.25 * float64(z))
|
||||
} else {
|
||||
pos.X += (0.42 * float64(x))
|
||||
pos.Z += (0.42 * float64(z))
|
||||
}
|
||||
|
||||
at = math.Atan2(-pos.X+float64(t.Pos.X)+0.5, -pos.Z+float64(t.Pos.Z)+0.5)
|
||||
out = Inputs{
|
||||
ThrottleX: math.Sin(at),
|
||||
ThrottleZ: math.Cos(at),
|
||||
Yaw: math.NaN(),
|
||||
}
|
||||
|
||||
case AscendNorth, AscendSouth, AscendEast, AscendWest:
|
||||
var (
|
||||
b = block.ByID[block.StateID[uint32(t.BlockStatus)]]
|
||||
_, isStairs = stairs[b.ID]
|
||||
_, isSlab = slabs[b.ID]
|
||||
maybeStuck = runTime < 1250*time.Millisecond
|
||||
dist2 = math.Sqrt(deltaPos.X*deltaPos.X + deltaPos.Z*deltaPos.Z)
|
||||
)
|
||||
out.Jump = dist2 < 1.75 && deltaPos.Y < -0.81
|
||||
|
||||
switch {
|
||||
case isStairs:
|
||||
// Special logic for stairs: Try to go towards the downwards edge initially.
|
||||
if dist2 > (0.9*0.9) && deltaPos.Y < 0 {
|
||||
if x, _, z := StairsDirection(t.BlockStatus).Offset(); dist2 > (0.9*0.9) && deltaPos.Y < 0 {
|
||||
pos.X += (0.49 * float64(x))
|
||||
pos.Z += (0.49 * float64(z))
|
||||
}
|
||||
|
||||
at = math.Atan2(-pos.X+float64(t.Pos.X)+0.5, -pos.Z+float64(t.Pos.Z)+0.5)
|
||||
out = Inputs{
|
||||
ThrottleX: math.Sin(at),
|
||||
ThrottleZ: math.Cos(at),
|
||||
Yaw: math.NaN(),
|
||||
Jump: out.Jump && !maybeStuck,
|
||||
}
|
||||
}
|
||||
// We dont need to jump for slabs, so only jump if we get stuck.
|
||||
case isSlab:
|
||||
out.Jump = out.Jump && !maybeStuck
|
||||
}
|
||||
|
||||
// Turn off the throttle if we get stuck on the jump.
|
||||
if dist2 < 1 && deltaPos.Y < 0 && vel.Y == 0 {
|
||||
out.ThrottleX, out.ThrottleZ = 0, 0
|
||||
}
|
||||
|
||||
case JumpCrossEast, JumpCrossWest, JumpCrossNorth, JumpCrossSouth:
|
||||
dist2 := math.Sqrt(deltaPos.X*deltaPos.X + deltaPos.Z*deltaPos.Z)
|
||||
out.Jump = dist2 > 1.5 && dist2 < 1.78
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t Tile) IsComplete(d Point) bool {
|
||||
switch t.Movement {
|
||||
case DescendLadder, DescendLadderNorth, DescendLadderSouth, DescendLadderWest, DescendLadderEast,
|
||||
DropNorth, DropSouth, DropEast, DropWest:
|
||||
return (d.X*d.X+d.Z*d.Z) < (2*0.2*0.25) && d.Y <= 0.05
|
||||
case AscendLadder:
|
||||
return d.Y >= 0
|
||||
case JumpCrossEast, JumpCrossWest, JumpCrossNorth, JumpCrossSouth:
|
||||
return (d.X*d.X+d.Z*d.Z) < (0.22*0.22) && d.Y >= -0.065
|
||||
}
|
||||
|
||||
yLowerCutoff := -0.065
|
||||
if t.HalfBlock {
|
||||
yLowerCutoff -= 0.5
|
||||
}
|
||||
|
||||
return (d.X*d.X+d.Z*d.Z) < (0.18*0.18) && d.Y >= yLowerCutoff && d.Y <= 0.08
|
||||
}
|
143
bot/phy/aabb.go
143
bot/phy/aabb.go
@ -1,143 +0,0 @@
|
||||
package phy
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/Tnze/go-mc/bot/world"
|
||||
)
|
||||
|
||||
type MinMax struct {
|
||||
Min, Max float64
|
||||
}
|
||||
|
||||
// Extends adjusts the bounds of the MinMax. A negative number will reduce the
|
||||
// minimum bound, whereas a positive number will increase the maximum bound.
|
||||
func (mm MinMax) Extend(delta float64) MinMax {
|
||||
if delta < 0 {
|
||||
return MinMax{
|
||||
Min: mm.Min + delta,
|
||||
Max: mm.Max,
|
||||
}
|
||||
}
|
||||
|
||||
return MinMax{
|
||||
Min: mm.Min,
|
||||
Max: mm.Max + delta,
|
||||
}
|
||||
}
|
||||
|
||||
// Contract reduces both the minimum and maximum bound by the provided amount,
|
||||
// such that the difference between the bounds decreases for positive values.
|
||||
func (mm MinMax) Contract(amt float64) MinMax {
|
||||
return MinMax{
|
||||
Min: mm.Min + amt,
|
||||
Max: mm.Max - amt,
|
||||
}
|
||||
}
|
||||
|
||||
// Expand changes the minimum and maximum bounds by the provided amount, such
|
||||
// that the difference between the bounds increases for positive values.
|
||||
func (mm MinMax) Expand(amt float64) MinMax {
|
||||
return MinMax{
|
||||
Min: mm.Min - amt,
|
||||
Max: mm.Max + amt,
|
||||
}
|
||||
}
|
||||
|
||||
// Offset adds the provided value to both the minimum and maximum value.
|
||||
func (mm MinMax) Offset(amt float64) MinMax {
|
||||
return MinMax{
|
||||
Min: mm.Min + amt,
|
||||
Max: mm.Max + amt,
|
||||
}
|
||||
}
|
||||
|
||||
// AABB implements Axis Aligned Bounding Box operations.
|
||||
type AABB struct {
|
||||
X, Y, Z MinMax
|
||||
Block world.BlockStatus
|
||||
}
|
||||
|
||||
// Extend adjusts the minimum (for negative values) or maximum bounds (for
|
||||
// positive values) by the provided scalar for each dimension.
|
||||
func (bb AABB) Extend(dx, dy, dz float64) AABB {
|
||||
return AABB{
|
||||
X: bb.X.Extend(dx),
|
||||
Y: bb.Y.Extend(dx),
|
||||
Z: bb.Z.Extend(dx),
|
||||
Block: bb.Block,
|
||||
}
|
||||
}
|
||||
|
||||
// Contract reduces the difference between the min/max bounds (for positive
|
||||
// values) for each dimension.
|
||||
func (bb AABB) Contract(x, y, z float64) AABB {
|
||||
return AABB{
|
||||
X: bb.X.Contract(x),
|
||||
Y: bb.Y.Contract(y),
|
||||
Z: bb.Z.Contract(z),
|
||||
Block: bb.Block,
|
||||
}
|
||||
}
|
||||
|
||||
// Expand increases both the minimum and maximum bounds by the provided amount
|
||||
// (for positive values) for each dimension.
|
||||
func (bb AABB) Expand(x, y, z float64) AABB {
|
||||
return AABB{
|
||||
X: bb.X.Expand(x),
|
||||
Y: bb.Y.Expand(y),
|
||||
Z: bb.Z.Expand(z),
|
||||
Block: bb.Block,
|
||||
}
|
||||
}
|
||||
|
||||
// Offset moves both the minimum and maximum bound by the provided value for
|
||||
// each dimension.
|
||||
func (bb AABB) Offset(x, y, z float64) AABB {
|
||||
return AABB{
|
||||
X: bb.X.Offset(x),
|
||||
Y: bb.Y.Offset(y),
|
||||
Z: bb.Z.Offset(z),
|
||||
Block: bb.Block,
|
||||
}
|
||||
}
|
||||
|
||||
func (bb AABB) XOffset(o AABB, xOffset float64) float64 {
|
||||
if o.Y.Max > bb.Y.Min && o.Y.Min < bb.Y.Max && o.Z.Max > bb.Z.Min && o.Z.Min < bb.Z.Max {
|
||||
if xOffset > 0.0 && o.X.Max <= bb.X.Min {
|
||||
xOffset = math.Min(bb.X.Min-o.X.Max, xOffset)
|
||||
} else if xOffset < 0.0 && o.X.Min >= bb.X.Max {
|
||||
xOffset = math.Max(bb.X.Max-o.X.Min, xOffset)
|
||||
}
|
||||
}
|
||||
return xOffset
|
||||
}
|
||||
|
||||
func (bb AABB) YOffset(o AABB, yOffset float64) float64 {
|
||||
if o.X.Max > bb.X.Min && o.X.Min < bb.X.Max && o.Z.Max > bb.Z.Min && o.Z.Min < bb.Z.Max {
|
||||
if yOffset > 0.0 && o.Y.Max <= bb.Y.Min {
|
||||
yOffset = math.Min(bb.Y.Min-o.Y.Max, yOffset)
|
||||
} else if yOffset < 0.0 && o.Y.Min >= bb.Y.Max {
|
||||
yOffset = math.Max(bb.Y.Max-o.Y.Min, yOffset)
|
||||
}
|
||||
}
|
||||
return yOffset
|
||||
}
|
||||
|
||||
func (bb AABB) ZOffset(o AABB, zOffset float64) float64 {
|
||||
if o.X.Max > bb.X.Min && o.X.Min < bb.X.Max && o.Y.Max > bb.Y.Min && o.Y.Min < bb.Y.Max {
|
||||
if zOffset > 0.0 && o.Z.Max <= bb.Z.Min {
|
||||
zOffset = math.Min(bb.Z.Min-o.Z.Max, zOffset)
|
||||
} else if zOffset < 0.0 && o.Z.Min >= bb.Z.Max {
|
||||
zOffset = math.Max(bb.Z.Max-o.Z.Min, zOffset)
|
||||
}
|
||||
}
|
||||
return zOffset
|
||||
}
|
||||
|
||||
func (bb AABB) Intersects(o AABB) bool {
|
||||
return true &&
|
||||
bb.X.Min < o.X.Max && bb.X.Max > o.X.Min &&
|
||||
bb.Y.Min < o.Y.Max && bb.Y.Max > o.Y.Min &&
|
||||
bb.Z.Min < o.Z.Max && bb.Z.Max > o.Z.Min
|
||||
}
|
288
bot/phy/phy.go
288
bot/phy/phy.go
@ -1,288 +0,0 @@
|
||||
// Package phy implements a minimal physics simulation necessary for realistic
|
||||
// bot behavior.
|
||||
package phy
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/Tnze/go-mc/bot/path"
|
||||
"github.com/Tnze/go-mc/bot/world"
|
||||
"github.com/Tnze/go-mc/bot/world/entity/player"
|
||||
"github.com/Tnze/go-mc/data/block/shape"
|
||||
)
|
||||
|
||||
const (
|
||||
playerWidth = 0.6
|
||||
playerHeight = 1.8
|
||||
resetVel = 0.003
|
||||
|
||||
maxYawChange = 11
|
||||
maxPitchChange = 7
|
||||
|
||||
stepHeight = 0.6
|
||||
minJumpTicks = 14
|
||||
ladderMaxSpeed = 0.15
|
||||
ladderClimbSpeed = 0.2
|
||||
|
||||
gravity = 0.08
|
||||
drag = 0.98
|
||||
acceleration = 0.02
|
||||
inertia = 0.91
|
||||
slipperiness = 0.6
|
||||
)
|
||||
|
||||
// World represents a provider of information about the surrounding world.
|
||||
type World interface {
|
||||
GetBlockStatus(x, y, z int) world.BlockStatus
|
||||
}
|
||||
|
||||
// Surrounds represents the blocks surrounding the player (Y, Z, X).
|
||||
type Surrounds []AABB
|
||||
|
||||
// State tracks physics state.
|
||||
type State struct {
|
||||
// player state.
|
||||
Pos path.Point
|
||||
Vel path.Point
|
||||
Yaw, Pitch float64
|
||||
lastJump uint32
|
||||
|
||||
// player state flags.
|
||||
onGround bool
|
||||
collision struct {
|
||||
vertical bool
|
||||
horizontal bool
|
||||
}
|
||||
|
||||
tick uint32
|
||||
Run bool
|
||||
}
|
||||
|
||||
func (s *State) ServerPositionUpdate(player player.Pos, w World) error {
|
||||
s.Pos = path.Point{X: player.X, Y: player.Y, Z: player.Z}
|
||||
s.Yaw, s.Pitch = float64(player.Yaw), float64(player.Pitch)
|
||||
s.Vel = path.Point{}
|
||||
s.onGround, s.collision.vertical, s.collision.horizontal = false, false, false
|
||||
s.Run = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func abs(i1, i2 int) int {
|
||||
if i1 < i2 {
|
||||
return i2 - i1
|
||||
}
|
||||
return i1 - i2
|
||||
}
|
||||
|
||||
func (s *State) surroundings(query AABB, w World) Surrounds {
|
||||
minY, maxY := int(math.Floor(query.Y.Min))-1, int(math.Floor(query.Y.Max))+1
|
||||
minZ, maxZ := int(math.Floor(query.Z.Min)), int(math.Floor(query.Z.Max))+1
|
||||
minX, maxX := int(math.Floor(query.X.Min)), int(math.Floor(query.X.Max))+1
|
||||
|
||||
out := Surrounds(make([]AABB, 0, abs(maxY, minY)*abs(maxZ, minZ)*abs(maxX, minX)*2))
|
||||
for y := minY; y < maxY; y++ {
|
||||
for z := minZ; z < maxZ; z++ {
|
||||
for x := minX; x < maxX; x++ {
|
||||
bStateID := w.GetBlockStatus(x, y, z)
|
||||
if !path.AirLikeBlock(bStateID) {
|
||||
bbs, err := shape.CollisionBoxes(bStateID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, box := range bbs {
|
||||
out = append(out, AABB{
|
||||
X: MinMax{Min: box.Min.X, Max: box.Max.X},
|
||||
Y: MinMax{Min: box.Min.Y, Max: box.Max.Y},
|
||||
Z: MinMax{Min: box.Min.Z, Max: box.Max.Z},
|
||||
Block: bStateID,
|
||||
}.Offset(float64(x), float64(y), float64(z)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *State) BB() AABB {
|
||||
return AABB{
|
||||
X: MinMax{Min: -playerWidth / 2, Max: playerWidth / 2},
|
||||
Y: MinMax{Max: playerHeight},
|
||||
Z: MinMax{Min: -playerWidth / 2, Max: playerWidth / 2},
|
||||
}.Offset(s.Pos.X, s.Pos.Y, s.Pos.Z)
|
||||
}
|
||||
|
||||
func (s *State) Position() player.Pos {
|
||||
return player.Pos{
|
||||
X: s.Pos.X, Y: s.Pos.Y, Z: s.Pos.Z,
|
||||
Yaw: float32(s.Yaw), Pitch: float32(s.Pitch),
|
||||
OnGround: s.onGround,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) Tick(input path.Inputs, w World) error {
|
||||
s.tick++
|
||||
if !s.Run {
|
||||
return nil
|
||||
}
|
||||
|
||||
var inertia = inertia
|
||||
var acceleration = acceleration
|
||||
if below := w.GetBlockStatus(int(math.Floor(s.Pos.X)), int(math.Floor(s.Pos.Y))-1, int(math.Floor(s.Pos.Z))); s.onGround && !path.AirLikeBlock(below) {
|
||||
inertia *= slipperiness
|
||||
acceleration = 0.1 * (0.1627714 / (inertia * inertia * inertia))
|
||||
}
|
||||
|
||||
s.tickVelocity(input, inertia, acceleration, w)
|
||||
s.tickPosition(w)
|
||||
|
||||
if path.IsLadder(w.GetBlockStatus(int(math.Floor(s.Pos.X)), int(math.Floor(s.Pos.Y)), int(math.Floor(s.Pos.Z)))) && s.collision.horizontal {
|
||||
s.Vel.Y = ladderClimbSpeed
|
||||
}
|
||||
|
||||
// Gravity
|
||||
s.Vel.Y -= gravity
|
||||
// Drag & friction.
|
||||
s.Vel.Y *= drag
|
||||
s.Vel.X *= inertia
|
||||
s.Vel.Z *= inertia
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *State) tickVelocity(input path.Inputs, inertia, acceleration float64, w World) {
|
||||
|
||||
// Deadzone velocities when they get too low.
|
||||
if math.Abs(s.Vel.X) < resetVel {
|
||||
s.Vel.X = 0
|
||||
}
|
||||
if math.Abs(s.Vel.Y) < resetVel {
|
||||
s.Vel.Y = 0
|
||||
}
|
||||
if math.Abs(s.Vel.Z) < resetVel {
|
||||
s.Vel.Z = 0
|
||||
}
|
||||
|
||||
s.applyLookInputs(input)
|
||||
s.applyPosInputs(input, acceleration, inertia)
|
||||
|
||||
lower := w.GetBlockStatus(int(math.Floor(s.Pos.X)), int(math.Floor(s.Pos.Y)), int(math.Floor(s.Pos.Z)))
|
||||
if path.IsLadder(lower) {
|
||||
s.Vel.X = math.Min(math.Max(-ladderMaxSpeed, s.Vel.X), ladderMaxSpeed)
|
||||
s.Vel.Z = math.Min(math.Max(-ladderMaxSpeed, s.Vel.Z), ladderMaxSpeed)
|
||||
s.Vel.Y = math.Min(math.Max(-ladderMaxSpeed, s.Vel.Y), ladderMaxSpeed)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) applyLookInputs(input path.Inputs) {
|
||||
if !math.IsNaN(input.Yaw) {
|
||||
errYaw := math.Min(math.Max(modYaw(input.Yaw, s.Yaw), -maxYawChange), maxYawChange)
|
||||
s.Yaw += errYaw
|
||||
}
|
||||
errPitch := math.Min(math.Max(input.Pitch-s.Pitch, -maxPitchChange), maxPitchChange)
|
||||
s.Pitch += errPitch
|
||||
}
|
||||
|
||||
func (s *State) applyPosInputs(input path.Inputs, acceleration, inertia float64) {
|
||||
if input.Jump && s.lastJump+minJumpTicks < s.tick && s.onGround {
|
||||
s.lastJump = s.tick
|
||||
s.Vel.Y = 0.42
|
||||
}
|
||||
|
||||
speed := math.Sqrt(input.ThrottleX*input.ThrottleX + input.ThrottleZ*input.ThrottleZ)
|
||||
if speed < 0.01 {
|
||||
return
|
||||
}
|
||||
speed = acceleration / math.Max(speed, 1)
|
||||
|
||||
input.ThrottleX *= speed
|
||||
input.ThrottleZ *= speed
|
||||
|
||||
s.Vel.X += input.ThrottleX
|
||||
s.Vel.Z += input.ThrottleZ
|
||||
}
|
||||
|
||||
func (s *State) tickPosition(w World) {
|
||||
p, newVel := s.computeCollisionYXZ(s.BB(), s.BB().Offset(s.Vel.X, s.Vel.Y, s.Vel.Z), s.Vel, w)
|
||||
|
||||
if s.onGround || (s.Vel.Y != newVel.Y && s.Vel.Y < 0) {
|
||||
bb := s.BB()
|
||||
surroundings := s.surroundings(bb.Offset(s.Vel.X, stepHeight, s.Vel.Z), w)
|
||||
outVel := s.Vel
|
||||
|
||||
outVel.Y = stepHeight
|
||||
for _, b := range surroundings {
|
||||
outVel.Y = b.YOffset(bb, outVel.Y)
|
||||
}
|
||||
bb = bb.Offset(0, outVel.Y, 0)
|
||||
for _, b := range surroundings {
|
||||
outVel.X = b.XOffset(bb, outVel.X)
|
||||
}
|
||||
bb = bb.Offset(outVel.X, 0, 0)
|
||||
for _, b := range surroundings {
|
||||
outVel.Z = b.ZOffset(bb, outVel.Z)
|
||||
}
|
||||
bb = bb.Offset(0, 0, outVel.Z)
|
||||
|
||||
outVel.Y *= -1
|
||||
// Lower the player back down to be on the ground.
|
||||
for _, b := range surroundings {
|
||||
outVel.Y = b.YOffset(bb, outVel.Y)
|
||||
}
|
||||
bb = bb.Offset(0, outVel.Y, 0)
|
||||
|
||||
oldMove := newVel.X*newVel.X + newVel.Z*newVel.Z
|
||||
newMove := outVel.X*outVel.X + outVel.Z*outVel.Z
|
||||
|
||||
if oldMove >= newMove || outVel.Y <= (0.000002-stepHeight) {
|
||||
// fmt.Println("nope")
|
||||
} else {
|
||||
p = bb
|
||||
newVel = outVel
|
||||
}
|
||||
}
|
||||
|
||||
// Update flags.
|
||||
s.Pos.X = p.X.Min + playerWidth/2
|
||||
s.Pos.Y = p.Y.Min
|
||||
s.Pos.Z = p.Z.Min + playerWidth/2
|
||||
s.collision.horizontal = newVel.X != s.Vel.X || newVel.Z != s.Vel.Z
|
||||
s.collision.vertical = newVel.Y != s.Vel.Y
|
||||
s.onGround = s.collision.vertical && s.Vel.Y < 0
|
||||
s.Vel = newVel
|
||||
}
|
||||
|
||||
func modYaw(new, old float64) float64 {
|
||||
delta := math.Mod(new-old, 360)
|
||||
if delta > 180 {
|
||||
delta = 180 - delta
|
||||
} else if delta < -180 {
|
||||
delta += 360
|
||||
}
|
||||
return delta
|
||||
}
|
||||
|
||||
func (s *State) computeCollisionYXZ(bb, query AABB, vel path.Point, w World) (outBB AABB, outVel path.Point) {
|
||||
surroundings := s.surroundings(query, w)
|
||||
outVel = vel
|
||||
|
||||
for _, b := range surroundings {
|
||||
outVel.Y = b.YOffset(bb, outVel.Y)
|
||||
}
|
||||
bb = bb.Offset(0, outVel.Y, 0)
|
||||
for _, b := range surroundings {
|
||||
outVel.X = b.XOffset(bb, outVel.X)
|
||||
}
|
||||
bb = bb.Offset(outVel.X, 0, 0)
|
||||
for _, b := range surroundings {
|
||||
outVel.Z = b.ZOffset(bb, outVel.Z)
|
||||
}
|
||||
bb = bb.Offset(0, 0, outVel.Z)
|
||||
return bb, outVel
|
||||
}
|
||||
|
||||
// AtLookTarget returns true if the player look position is actually at the
|
||||
// given pitch and yaw.
|
||||
func (s *State) AtLookTarget(yaw, pitch float64) bool {
|
||||
dYaw, dPitch := math.Abs(modYaw(yaw, s.Yaw)), math.Abs(pitch-s.Pitch)
|
||||
return dYaw <= 0.8 && dPitch <= 1.1
|
||||
}
|
@ -2,9 +2,12 @@ package bot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Tnze/go-mc/net"
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
mcnet "github.com/Tnze/go-mc/net"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
@ -12,49 +15,70 @@ import (
|
||||
// Returns a JSON data with server status, and the delay.
|
||||
//
|
||||
// For more information for JSON format, see https://wiki.vg/Server_List_Ping#Response
|
||||
func PingAndList(addr string, port int) ([]byte, time.Duration, error) {
|
||||
conn, err := net.DialMC(fmt.Sprintf("%s:%d", addr, port))
|
||||
func PingAndList(addr string) ([]byte, time.Duration, error) {
|
||||
addrSrv, err := parseAddress(&net.Resolver{}, addr)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("bot: dial fail: %v", err)
|
||||
return nil, 0, LoginErr{"parse address", err}
|
||||
}
|
||||
return pingAndList(addr, port, conn)
|
||||
|
||||
conn, err := mcnet.DialMC(addrSrv)
|
||||
if err != nil {
|
||||
return nil, 0, LoginErr{"dial connection", err}
|
||||
}
|
||||
return pingAndList(addr, conn)
|
||||
}
|
||||
|
||||
// PingAndListTimeout PingAndLIstTimeout is the version of PingAndList with max request time.
|
||||
func PingAndListTimeout(addr string, port int, timeout time.Duration) ([]byte, time.Duration, error) {
|
||||
func PingAndListTimeout(addr string, timeout time.Duration) ([]byte, time.Duration, error) {
|
||||
deadLine := time.Now().Add(timeout)
|
||||
|
||||
conn, err := net.DialMCTimeout(fmt.Sprintf("%s:%d", addr, port), timeout)
|
||||
addrSrv, err := parseAddress(&net.Resolver{}, addr)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("bot: dial fail: %v", err)
|
||||
return nil, 0, LoginErr{"parse address", err}
|
||||
}
|
||||
|
||||
conn, err := mcnet.DialMCTimeout(addrSrv, timeout)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = conn.Socket.SetDeadline(deadLine)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("bot: set deadline fail: %v", err)
|
||||
return nil, 0, LoginErr{"set deadline", err}
|
||||
}
|
||||
|
||||
return pingAndList(addr, port, conn)
|
||||
return pingAndList(addr, conn)
|
||||
}
|
||||
|
||||
func pingAndList(addr string, port int, conn *net.Conn) ([]byte, time.Duration, error) {
|
||||
func pingAndList(addr string, conn *mcnet.Conn) ([]byte, time.Duration, error) {
|
||||
// Split Host and Port
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, 0, LoginErr{"split address", err}
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
return nil, 0, LoginErr{"parse port", err}
|
||||
}
|
||||
|
||||
const Handshake = 0x00
|
||||
//握手
|
||||
err := conn.WritePacket(
|
||||
//Handshake Packet
|
||||
pk.Marshal(
|
||||
0x00, //Handshake packet ID
|
||||
pk.VarInt(ProtocolVersion), //Protocol version
|
||||
pk.String(addr), //Server's address
|
||||
pk.UnsignedShort(port),
|
||||
pk.Byte(1),
|
||||
))
|
||||
err = conn.WritePacket(pk.Marshal(
|
||||
Handshake, //Handshake packet ID
|
||||
pk.VarInt(ProtocolVersion), //Protocol version
|
||||
pk.String(host), //Server's address
|
||||
pk.UnsignedShort(port),
|
||||
pk.Byte(1),
|
||||
))
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("bot: send handshake packect fail: %v", err)
|
||||
}
|
||||
|
||||
//LIST
|
||||
//请求服务器状态
|
||||
err = conn.WritePacket(pk.Marshal(0))
|
||||
err = conn.WritePacket(pk.Marshal(
|
||||
packetid.PingStart,
|
||||
))
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("bot: send list packect fail: %v", err)
|
||||
}
|
||||
@ -72,7 +96,10 @@ func pingAndList(addr string, port int, conn *net.Conn) ([]byte, time.Duration,
|
||||
|
||||
//PING
|
||||
startTime := time.Now()
|
||||
err = conn.WritePacket(pk.Marshal(0x01, pk.Long(startTime.Unix())))
|
||||
err = conn.WritePacket(pk.Marshal(
|
||||
packetid.PingServerbound,
|
||||
pk.Long(startTime.Unix()),
|
||||
))
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("bot: send ping packect fail: %v", err)
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
package world
|
||||
|
||||
// bitArray implements a bitfield array where values are packed into uint64
|
||||
// values. If the next value does not fit into remaining space, the remaining
|
||||
// space of a uint64 is unused.
|
||||
type bitArray struct {
|
||||
width uint // bit width of each value
|
||||
valuesPerElement uint // number of values which fit into a single uint64.
|
||||
|
||||
data []uint64
|
||||
}
|
||||
|
||||
// Size returns the number of elements that can fit into the bit array.
|
||||
func (b *bitArray) Size() int {
|
||||
return int(b.valuesPerElement) * len(b.data)
|
||||
}
|
||||
|
||||
func (b *bitArray) Set(idx, val uint) {
|
||||
var (
|
||||
arrayIdx = idx / b.valuesPerElement
|
||||
startBit = (idx % b.valuesPerElement) * b.width
|
||||
mask = ^uint64((1<<b.width - 1) << startBit) // set for all bits except target
|
||||
)
|
||||
b.data[arrayIdx] = (b.data[arrayIdx] & mask) | uint64(val<<startBit)
|
||||
}
|
||||
|
||||
func (b *bitArray) Get(idx uint) uint {
|
||||
var (
|
||||
arrayIdx = idx / b.valuesPerElement
|
||||
offset = (idx % b.valuesPerElement) * b.width
|
||||
mask = uint64((1<<b.width - 1) << offset) // set for just the target
|
||||
)
|
||||
return uint(b.data[arrayIdx]&mask) >> offset
|
||||
}
|
||||
|
||||
func valuesPerBitArrayElement(bitsPerValue uint) uint {
|
||||
return uint(64 / bitsPerValue)
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package world
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBitArrayBasic(t *testing.T) {
|
||||
a := bitArray{
|
||||
width: 5,
|
||||
valuesPerElement: valuesPerBitArrayElement(5),
|
||||
data: make([]uint64, 5),
|
||||
}
|
||||
|
||||
if got, want := a.Size(), 12*5; got != want {
|
||||
t.Errorf("size = %d, want %d", got, want)
|
||||
}
|
||||
|
||||
a.Set(0, 4)
|
||||
if v := a.Get(0); v != 4 {
|
||||
t.Errorf("v[0] = %d, want 4", v)
|
||||
}
|
||||
a.Set(12, 8)
|
||||
if v := a.Get(12); v != 8 {
|
||||
t.Errorf("v[12] = %d, want 8", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBitArrayHardcoded(t *testing.T) {
|
||||
d1, _ := hex.DecodeString("0020863148418841")
|
||||
d2, _ := hex.DecodeString("01018A7260F68C87")
|
||||
|
||||
a := bitArray{
|
||||
width: 5,
|
||||
valuesPerElement: valuesPerBitArrayElement(5),
|
||||
data: []uint64{binary.BigEndian.Uint64(d1), binary.BigEndian.Uint64(d2)},
|
||||
}
|
||||
|
||||
if got, want := a.Size(), 12*2; got != want {
|
||||
t.Errorf("size = %d, want %d", got, want)
|
||||
}
|
||||
|
||||
want := []uint{1, 2, 2, 3, 4, 4, 5, 6, 6, 4, 8, 0, 7, 4, 3, 13, 15, 16, 9, 14, 10, 12, 0, 2}
|
||||
for idx, want := range want {
|
||||
if got := a.Get(uint(idx)); got != want {
|
||||
t.Errorf("v[%d] = %d, want %d", idx, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
package world
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/Tnze/go-mc/data/block"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
const maxPaletteBits = 8
|
||||
|
||||
// DecodeChunkColumn decode the chunk data structure.
|
||||
// If decoding went error, successful decoded data will be returned.
|
||||
func DecodeChunkColumn(mask int32, data []byte) (*Chunk, error) {
|
||||
var c Chunk
|
||||
r := bytes.NewReader(data)
|
||||
for sectionY := 0; sectionY < 16; sectionY++ {
|
||||
// If the section's bit set in the mask
|
||||
if (mask & (1 << uint(sectionY))) != 0 {
|
||||
// read section
|
||||
sec, err := readSection(r)
|
||||
if err != nil {
|
||||
return &c, fmt.Errorf("read section[%d] error: %w", sectionY, err)
|
||||
}
|
||||
c.Sections[sectionY] = sec
|
||||
}
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func perBits(bpb byte) uint {
|
||||
switch {
|
||||
case bpb <= 4:
|
||||
return 4
|
||||
case bpb <= maxPaletteBits:
|
||||
return uint(bpb)
|
||||
default:
|
||||
return uint(block.BitsPerBlock)
|
||||
}
|
||||
}
|
||||
|
||||
func readSection(data io.Reader) (s Section, err error) {
|
||||
var nonAirBlockCount pk.Short
|
||||
if _, err := nonAirBlockCount.ReadFrom(data); err != nil {
|
||||
return nil, fmt.Errorf("block count: %w", err)
|
||||
}
|
||||
var bpb pk.UnsignedByte
|
||||
if _, err := bpb.ReadFrom(data); err != nil {
|
||||
return nil, fmt.Errorf("bits per block: %w", err)
|
||||
}
|
||||
// If bpb values greater than or equal to 9, use directSection.
|
||||
// Otherwise use paletteSection.
|
||||
var palettes []BlockStatus
|
||||
var palettesIndex map[BlockStatus]int
|
||||
if bpb <= maxPaletteBits {
|
||||
// read palettes
|
||||
var length pk.VarInt
|
||||
if _, err := length.ReadFrom(data); err != nil {
|
||||
return nil, fmt.Errorf("palette length: %w", err)
|
||||
}
|
||||
palettes = make([]BlockStatus, length)
|
||||
palettesIndex = make(map[BlockStatus]int, length)
|
||||
for i := 0; i < int(length); i++ {
|
||||
var v pk.VarInt
|
||||
if _, err := v.ReadFrom(data); err != nil {
|
||||
return nil, fmt.Errorf("read palettes[%d] error: %w", i, err)
|
||||
}
|
||||
palettes[i] = BlockStatus(v)
|
||||
palettesIndex[BlockStatus(v)] = i
|
||||
}
|
||||
}
|
||||
|
||||
// read data array
|
||||
var dataLen pk.VarInt
|
||||
if _, err := dataLen.ReadFrom(data); err != nil {
|
||||
return nil, fmt.Errorf("read data array length error: %w", err)
|
||||
}
|
||||
if int(dataLen) < 16*16*16*int(bpb)/64 {
|
||||
return nil, fmt.Errorf("data length (%d) is not enough of given bpb (%d)", dataLen, bpb)
|
||||
}
|
||||
dataArray := make([]uint64, dataLen)
|
||||
for i := 0; i < int(dataLen); i++ {
|
||||
var v pk.Long
|
||||
if _, err := v.ReadFrom(data); err != nil {
|
||||
return nil, fmt.Errorf("read dataArray[%d] error: %w", i, err)
|
||||
}
|
||||
dataArray[i] = uint64(v)
|
||||
}
|
||||
|
||||
width := perBits(byte(bpb))
|
||||
sec := directSection{
|
||||
bitArray{
|
||||
width: width,
|
||||
valuesPerElement: valuesPerBitArrayElement(width),
|
||||
data: dataArray,
|
||||
},
|
||||
}
|
||||
if bpb <= maxPaletteBits {
|
||||
return &paletteSection{
|
||||
palette: palettes,
|
||||
palettesIndex: palettesIndex,
|
||||
directSection: sec,
|
||||
}, nil
|
||||
} else {
|
||||
return &sec, nil
|
||||
}
|
||||
}
|
||||
|
||||
type directSection struct {
|
||||
bitArray
|
||||
}
|
||||
|
||||
func (d *directSection) GetBlock(offset uint) BlockStatus {
|
||||
return BlockStatus(d.Get(offset))
|
||||
}
|
||||
|
||||
func (d *directSection) SetBlock(offset uint, s BlockStatus) {
|
||||
d.Set(offset, uint(s))
|
||||
}
|
||||
|
||||
func (d *directSection) CanContain(s BlockStatus) bool {
|
||||
return s <= (1<<d.width - 1)
|
||||
}
|
||||
|
||||
func (d *directSection) clone(bpb uint) *directSection {
|
||||
out := newSectionWithSize(bpb)
|
||||
for offset := uint(0); offset < 16*16*16; offset++ {
|
||||
out.SetBlock(offset, d.GetBlock(offset))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func newSectionWithSize(bpb uint) *directSection {
|
||||
valuesPerElement := valuesPerBitArrayElement(bpb)
|
||||
return &directSection{
|
||||
bitArray{
|
||||
width: bpb,
|
||||
valuesPerElement: valuesPerElement,
|
||||
data: make([]uint64, int(math.Ceil(16*16*16/float64(valuesPerElement)))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type paletteSection struct {
|
||||
palette []BlockStatus
|
||||
palettesIndex map[BlockStatus]int
|
||||
directSection
|
||||
}
|
||||
|
||||
func (p *paletteSection) GetBlock(offset uint) BlockStatus {
|
||||
v := p.directSection.GetBlock(offset)
|
||||
return p.palette[v]
|
||||
}
|
||||
|
||||
func (p *paletteSection) SetBlock(offset uint, s BlockStatus) {
|
||||
if i, ok := p.palettesIndex[s]; ok {
|
||||
p.directSection.SetBlock(offset, BlockStatus(i))
|
||||
return
|
||||
}
|
||||
i := len(p.palette)
|
||||
p.palette = append(p.palette, s)
|
||||
p.palettesIndex[s] = i
|
||||
if !p.directSection.CanContain(BlockStatus(i)) {
|
||||
// Increase the underlying directSection
|
||||
// Suppose that old bpb fit len(p.palette) before it appended.
|
||||
// So bpb+1 must enough for new len(p.palette).
|
||||
p.directSection = *p.directSection.clone(p.width + 1)
|
||||
}
|
||||
p.directSection.SetBlock(offset, BlockStatus(i))
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/Tnze/go-mc/data/entity"
|
||||
item "github.com/Tnze/go-mc/data/item"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// BlockEntity describes the representation of a tile entity at a position.
|
||||
type BlockEntity struct {
|
||||
ID string `nbt:"id"`
|
||||
|
||||
// global co-ordinates
|
||||
X int `nbt:"x"`
|
||||
Y int `nbt:"y"`
|
||||
Z int `nbt:"z"`
|
||||
|
||||
// sign-specific.
|
||||
Color string `nbt:"color"`
|
||||
Text1 string `nbt:"Text1"`
|
||||
Text2 string `nbt:"Text2"`
|
||||
Text3 string `nbt:"Text3"`
|
||||
Text4 string `nbt:"Text4"`
|
||||
}
|
||||
|
||||
//Entity represents an instance of an entity.
|
||||
type Entity struct {
|
||||
ID int32
|
||||
Data int32
|
||||
Base *entity.Entity
|
||||
|
||||
UUID uuid.UUID
|
||||
|
||||
X, Y, Z float64
|
||||
Pitch, Yaw int8
|
||||
VelX, VelY, VelZ int16
|
||||
OnGround bool
|
||||
|
||||
IsLiving bool
|
||||
HeadPitch int8
|
||||
}
|
||||
|
||||
// The Slot data structure is how Minecraft represents an item and its associated data in the Minecraft Protocol
|
||||
type Slot struct {
|
||||
Present bool
|
||||
ItemID item.ID
|
||||
Count int8
|
||||
NBT pk.NBT
|
||||
}
|
||||
|
||||
type SlotNBT struct {
|
||||
data interface{}
|
||||
}
|
||||
|
||||
//Decode implement packet.FieldDecoder interface
|
||||
func (s *Slot) ReadFrom(r io.Reader) (int64, error) {
|
||||
var itemID pk.VarInt
|
||||
n, err := pk.Tuple{
|
||||
(*pk.Boolean)(&s.Present),
|
||||
pk.Opt{
|
||||
Has: (*pk.Boolean)(&s.Present),
|
||||
Field: pk.Tuple{
|
||||
&itemID,
|
||||
(*pk.Byte)(&s.Count),
|
||||
&s.NBT,
|
||||
},
|
||||
},
|
||||
}.ReadFrom(r)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
s.ItemID = item.ID(itemID)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (s Slot) WriteTo(w io.Writer) (int64, error) {
|
||||
return pk.Tuple{
|
||||
pk.Boolean(s.Present),
|
||||
pk.Opt{
|
||||
Has: (*pk.Boolean)(&s.Present),
|
||||
Field: pk.Tuple{
|
||||
pk.VarInt(s.ItemID),
|
||||
pk.Byte(s.Count),
|
||||
s.NBT,
|
||||
},
|
||||
},
|
||||
}.WriteTo(w)
|
||||
}
|
||||
|
||||
func (s Slot) String() string {
|
||||
return item.ByID[s.ItemID].DisplayName
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
package player
|
||||
|
||||
import "github.com/Tnze/go-mc/bot/world/entity"
|
||||
|
||||
type Pos struct {
|
||||
X, Y, Z float64
|
||||
Yaw, Pitch float32
|
||||
OnGround bool
|
||||
}
|
||||
|
||||
func (p Pos) PosEqual(other Pos) bool {
|
||||
return p.X == other.X && p.Y == other.Y && p.Z == other.Z
|
||||
}
|
||||
func (p Pos) LookEqual(other Pos) bool {
|
||||
return p.Yaw == other.Yaw && p.Pitch == other.Pitch
|
||||
}
|
||||
func (p Pos) Equal(other Pos) bool {
|
||||
return p.PosEqual(other) && p.LookEqual(other) && p.OnGround == other.OnGround
|
||||
}
|
||||
|
||||
// Player includes the player's status.
|
||||
type Player struct {
|
||||
entity.Entity
|
||||
UUID [2]int64 //128bit UUID
|
||||
|
||||
Pos Pos
|
||||
|
||||
HeldItem int //拿着的物品栏位
|
||||
|
||||
Health float32 //血量
|
||||
Food int32 //饱食度
|
||||
FoodSaturation float32 //食物饱和度
|
||||
|
||||
Level int32
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package world
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TilePosition describes the location of a tile/block entity within a chunk.
|
||||
type TilePosition uint32
|
||||
|
||||
func (p TilePosition) Pos() (x, y, z int) {
|
||||
return int((p >> 8) & 0xff), int((p >> 16) & 0xff), int(p & 0xff)
|
||||
}
|
||||
|
||||
func (p TilePosition) String() string {
|
||||
x, y, z := p.Pos()
|
||||
return fmt.Sprintf("(%d, %d, %d)", x, y, z)
|
||||
}
|
||||
|
||||
func ToTilePos(x, y, z int) TilePosition {
|
||||
return TilePosition((y&0xff)<<16 | (x&15)<<8 | (z & 15))
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package world
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/Tnze/go-mc/bot/world/entity"
|
||||
)
|
||||
|
||||
// World record all of the things in the world where player at
|
||||
type World struct {
|
||||
entityLock sync.RWMutex
|
||||
Entities map[int32]*entity.Entity
|
||||
chunkLock sync.RWMutex
|
||||
Chunks map[ChunkLoc]*Chunk
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
package world
|
||||
|
||||
import (
|
||||
"github.com/Tnze/go-mc/bot/world/entity"
|
||||
"github.com/Tnze/go-mc/data/block"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/Tnze/go-mc/net/ptypes"
|
||||
)
|
||||
|
||||
// Chunk store a 256*16*16 area of blocks, sharded on the Y axis into 16
|
||||
// sections.
|
||||
type Chunk struct {
|
||||
Sections [16]Section
|
||||
TileEntities map[TilePosition]entity.BlockEntity
|
||||
}
|
||||
|
||||
// Section implements storage of blocks within a fixed 16*16*16 area.
|
||||
type Section interface {
|
||||
// GetBlock return block status, offset can be calculate by SectionOffset.
|
||||
GetBlock(offset uint) BlockStatus
|
||||
// SetBlock is the reverse operation of GetBlock.
|
||||
SetBlock(offset uint, s BlockStatus)
|
||||
}
|
||||
|
||||
func sectionIdx(x, y, z int) uint {
|
||||
return uint(((y & 15) << 8) | (z << 4) | x)
|
||||
}
|
||||
|
||||
type BlockStatus uint32
|
||||
|
||||
type ChunkLoc struct {
|
||||
X, Z int
|
||||
}
|
||||
|
||||
// Signs returns a list of signs on the server within viewing range.
|
||||
func (w *World) Signs() []entity.BlockEntity {
|
||||
w.chunkLock.RLock()
|
||||
defer w.chunkLock.RUnlock()
|
||||
|
||||
out := make([]entity.BlockEntity, 0, 4)
|
||||
for _, c := range w.Chunks {
|
||||
for _, e := range c.TileEntities {
|
||||
if e.ID == "minecraft:sign" {
|
||||
out = append(out, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// GetBlockStatus return the state ID of the block at the given position.
|
||||
func (w *World) GetBlockStatus(x, y, z int) BlockStatus {
|
||||
w.chunkLock.RLock()
|
||||
defer w.chunkLock.RUnlock()
|
||||
|
||||
c := w.Chunks[ChunkLoc{x >> 4, z >> 4}]
|
||||
if c != nil && y >= 0 {
|
||||
if sec := c.Sections[y>>4]; sec != nil {
|
||||
return sec.GetBlock(sectionIdx(x&15, y&15, z&15))
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// UnloadChunk unloads a chunk from the world.
|
||||
func (w *World) UnloadChunk(loc ChunkLoc) {
|
||||
w.chunkLock.Lock()
|
||||
delete(w.Chunks, loc)
|
||||
w.chunkLock.Unlock()
|
||||
}
|
||||
|
||||
// UnaryBlockUpdate updates the block at the specified position with a new
|
||||
// state ID.
|
||||
func (w *World) UnaryBlockUpdate(pos pk.Position, bStateID BlockStatus) bool {
|
||||
w.chunkLock.Lock()
|
||||
defer w.chunkLock.Unlock()
|
||||
|
||||
c := w.Chunks[ChunkLoc{X: pos.X >> 4, Z: pos.Z >> 4}]
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
sIdx, bIdx := pos.Y>>4, sectionIdx(pos.X&15, pos.Y&15, pos.Z&15)
|
||||
|
||||
if sec := c.Sections[sIdx]; sec == nil {
|
||||
sec = newSectionWithSize(uint(block.BitsPerBlock))
|
||||
sec.SetBlock(bIdx, bStateID)
|
||||
c.Sections[sIdx] = sec
|
||||
} else {
|
||||
tp := ToTilePos(pos.X, pos.Y, pos.Z)
|
||||
if _, ok := c.TileEntities[tp]; ok && sec.GetBlock(bIdx) != bStateID {
|
||||
delete(c.TileEntities, tp)
|
||||
}
|
||||
sec.SetBlock(bIdx, bStateID)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MultiBlockUpdate updates a packed specification of blocks within a single
|
||||
// section of a chunk.
|
||||
func (w *World) MultiBlockUpdate(loc ChunkLoc, sectionY int, blocks []pk.VarLong) bool {
|
||||
w.chunkLock.Lock()
|
||||
defer w.chunkLock.Unlock()
|
||||
|
||||
c := w.Chunks[loc]
|
||||
if c == nil {
|
||||
return false // not loaded
|
||||
}
|
||||
|
||||
sec := c.Sections[sectionY]
|
||||
if sec == nil {
|
||||
sec = newSectionWithSize(uint(block.BitsPerBlock))
|
||||
c.Sections[sectionY] = sec
|
||||
}
|
||||
|
||||
for _, b := range blocks {
|
||||
var (
|
||||
bStateID = BlockStatus(b >> 12)
|
||||
x, z, y = (b >> 8) & 0xf, (b >> 4) & 0xf, b & 0xf
|
||||
bIdx = sectionIdx(int(x&15), int(y&15), int(z&15))
|
||||
tp = ToTilePos(int(x), int(y), int(z))
|
||||
)
|
||||
if _, ok := c.TileEntities[tp]; ok && sec.GetBlock(bIdx) != bStateID {
|
||||
delete(c.TileEntities, tp)
|
||||
}
|
||||
sec.SetBlock(bIdx, bStateID)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
//LoadChunk adds the given chunk to the world.
|
||||
func (w *World) LoadChunk(x, z int, c *Chunk) {
|
||||
w.chunkLock.Lock()
|
||||
w.Chunks[ChunkLoc{X: x, Z: z}] = c
|
||||
w.chunkLock.Unlock()
|
||||
}
|
||||
|
||||
func (w *World) TileEntityUpdate(pkt ptypes.TileEntityData) error {
|
||||
w.chunkLock.Lock()
|
||||
defer w.chunkLock.Unlock()
|
||||
c := w.Chunks[ChunkLoc{X: pkt.Pos.X >> 4, Z: pkt.Pos.Z >> 4}]
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch pkt.Action {
|
||||
case 9: // Sign update
|
||||
c.TileEntities[ToTilePos(pkt.Pos.X, pkt.Pos.Y, pkt.Pos.Z)] = pkt.Data
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
package world
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Tnze/go-mc/bot/world/entity"
|
||||
e "github.com/Tnze/go-mc/data/entity"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/Tnze/go-mc/net/ptypes"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// PlayerEntities returns a list of players on the server within viewing range.
|
||||
func (w *World) PlayerEntities() []entity.Entity {
|
||||
w.entityLock.RLock()
|
||||
defer w.entityLock.RUnlock()
|
||||
out := make([]entity.Entity, 0, 12)
|
||||
for _, ent := range w.Entities {
|
||||
if ent.Base.ID == e.Player.ID {
|
||||
out = append(out, *ent)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// OnSpawnEntity should be called when a SpawnEntity packet
|
||||
// is received.
|
||||
func (w *World) OnSpawnEntity(pkt ptypes.SpawnEntity) error {
|
||||
w.entityLock.Lock()
|
||||
defer w.entityLock.Unlock()
|
||||
|
||||
base, ok := e.ByID[e.ID(pkt.Type)]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown entity ID %v", pkt.Type)
|
||||
}
|
||||
|
||||
w.Entities[int32(pkt.ID)] = &entity.Entity{
|
||||
ID: int32(pkt.ID),
|
||||
Base: base,
|
||||
Data: int32(pkt.Data),
|
||||
UUID: uuid.UUID(pkt.UUID),
|
||||
X: float64(pkt.X),
|
||||
Y: float64(pkt.Y),
|
||||
Z: float64(pkt.Z),
|
||||
Pitch: int8(pkt.Pitch),
|
||||
Yaw: int8(pkt.Yaw),
|
||||
VelX: int16(pkt.VelX),
|
||||
VelY: int16(pkt.VelY),
|
||||
VelZ: int16(pkt.VelZ),
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnSpawnLivingEntity should be called when a SpawnLivingEntity packet
|
||||
// is received.
|
||||
func (w *World) OnSpawnLivingEntity(pkt ptypes.SpawnLivingEntity) error {
|
||||
w.entityLock.Lock()
|
||||
defer w.entityLock.Unlock()
|
||||
|
||||
base, ok := e.ByID[e.ID(pkt.Type)]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown entity ID %v", pkt.Type)
|
||||
}
|
||||
|
||||
w.Entities[int32(pkt.ID)] = &entity.Entity{
|
||||
ID: int32(pkt.ID),
|
||||
Base: base,
|
||||
UUID: uuid.UUID(pkt.UUID),
|
||||
X: float64(pkt.X),
|
||||
Y: float64(pkt.Y),
|
||||
Z: float64(pkt.Z),
|
||||
Pitch: int8(pkt.Pitch),
|
||||
Yaw: int8(pkt.Yaw),
|
||||
VelX: int16(pkt.VelX),
|
||||
VelY: int16(pkt.VelY),
|
||||
VelZ: int16(pkt.VelZ),
|
||||
HeadPitch: int8(pkt.HeadPitch),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnSpawnPlayer should be called when a SpawnPlayer packet
|
||||
// is received.
|
||||
func (w *World) OnSpawnPlayer(pkt ptypes.SpawnPlayer) error {
|
||||
w.entityLock.Lock()
|
||||
defer w.entityLock.Unlock()
|
||||
|
||||
w.Entities[int32(pkt.ID)] = &entity.Entity{
|
||||
ID: int32(pkt.ID),
|
||||
Base: &e.Player,
|
||||
UUID: uuid.UUID(pkt.UUID),
|
||||
X: float64(pkt.X),
|
||||
Y: float64(pkt.Y),
|
||||
Z: float64(pkt.Z),
|
||||
Pitch: int8(pkt.Pitch),
|
||||
Yaw: int8(pkt.Yaw),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnEntityPosUpdate should be called when an EntityPosition packet
|
||||
// is received.
|
||||
func (w *World) OnEntityPosUpdate(pkt ptypes.EntityPosition) error {
|
||||
w.entityLock.Lock()
|
||||
defer w.entityLock.Unlock()
|
||||
|
||||
ent, ok := w.Entities[int32(pkt.ID)]
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot handle position update for unknown entity %d", pkt.ID)
|
||||
}
|
||||
|
||||
ent.X += float64(pkt.X) / (128 * 32)
|
||||
ent.Y += float64(pkt.Y) / (128 * 32)
|
||||
ent.Z += float64(pkt.Z) / (128 * 32)
|
||||
ent.OnGround = bool(pkt.OnGround)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnEntityPosLookUpdate should be called when an EntityPositionLook packet
|
||||
// is received.
|
||||
func (w *World) OnEntityPosLookUpdate(pkt ptypes.EntityPositionLook) error {
|
||||
w.entityLock.Lock()
|
||||
defer w.entityLock.Unlock()
|
||||
|
||||
ent, ok := w.Entities[int32(pkt.ID)]
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot handle position look update for unknown entity %d", pkt.ID)
|
||||
}
|
||||
|
||||
ent.X += float64(pkt.X) / (128 * 32)
|
||||
ent.Y += float64(pkt.Y) / (128 * 32)
|
||||
ent.Z += float64(pkt.Z) / (128 * 32)
|
||||
ent.OnGround = bool(pkt.OnGround)
|
||||
ent.Pitch, ent.Yaw = int8(pkt.Pitch), int8(pkt.Yaw)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnEntityLookUpdate should be called when an EntityRotation packet
|
||||
// is received.
|
||||
func (w *World) OnEntityLookUpdate(pkt ptypes.EntityRotation) error {
|
||||
w.entityLock.Lock()
|
||||
defer w.entityLock.Unlock()
|
||||
|
||||
ent, ok := w.Entities[int32(pkt.ID)]
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot handle look update for unknown entity %d", pkt.ID)
|
||||
}
|
||||
|
||||
ent.Pitch, ent.Yaw = int8(pkt.Pitch), int8(pkt.Yaw)
|
||||
ent.OnGround = bool(pkt.OnGround)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnEntityDestroy should be called when a DestroyEntities packet
|
||||
// is received.
|
||||
func (w *World) OnEntityDestroy(eIDs []pk.VarInt) error {
|
||||
w.entityLock.Lock()
|
||||
defer w.entityLock.Unlock()
|
||||
|
||||
for _, eID := range eIDs {
|
||||
delete(w.Entities, int32(eID))
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,49 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/Tnze/go-mc/bot"
|
||||
"github.com/Tnze/go-mc/bot/basic"
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
_ "github.com/Tnze/go-mc/data/lang/zh-cn"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
var address = flag.String("address", "127.0.0.1", "The server address")
|
||||
var c *bot.Client
|
||||
var client *bot.Client
|
||||
var player *basic.Player
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
c = bot.NewClient()
|
||||
client = bot.NewClient()
|
||||
player = basic.NewPlayer(client, basic.DefaultSettings)
|
||||
basic.EventsListener{
|
||||
GameStart: onGameStart,
|
||||
ChatMsg: onChatMsg,
|
||||
Disconnect: onDisconnect,
|
||||
Death: onDeath,
|
||||
}.Attach(client)
|
||||
|
||||
//Login
|
||||
err := c.JoinServer(*address)
|
||||
err := client.JoinServer(*address)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println("Login success")
|
||||
|
||||
//Register event handlers
|
||||
c.Events.GameStart = onGameStart
|
||||
c.Events.ChatMsg = onChatMsg
|
||||
c.Events.Disconnect = onDisconnect
|
||||
c.Events.PluginMessage = onPluginMessage
|
||||
|
||||
//JoinGame
|
||||
err = c.HandleGame()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
for {
|
||||
if err = client.HandleGame(); err != nil {
|
||||
var interErr *bot.PacketHandlerError
|
||||
if errors.As(err, &interErr) {
|
||||
log.Print("Internal bugs: ", interErr)
|
||||
} else {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func onDeath() error {
|
||||
log.Println("Died and Respawned")
|
||||
// If we exclude Respawn(...) then the player won't press the "Respawn" button upon death
|
||||
return c.Respawn()
|
||||
return player.Respawn()
|
||||
}
|
||||
|
||||
func onGameStart() error {
|
||||
@ -56,22 +68,7 @@ func onChatMsg(c chat.Message, pos byte, uuid uuid.UUID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func onDisconnect(c chat.Message) error {
|
||||
log.Println("Disconnect:", c)
|
||||
return nil
|
||||
}
|
||||
|
||||
func onPluginMessage(channel string, data []byte) error {
|
||||
switch channel {
|
||||
case "minecraft:brand":
|
||||
var brand pk.String
|
||||
if _, err := brand.ReadFrom(bytes.NewReader(data)); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println("Server brand is:", brand)
|
||||
|
||||
default:
|
||||
log.Println("PluginMessage", channel, data)
|
||||
}
|
||||
func onDisconnect(reason chat.Message) error {
|
||||
log.Println("Disconnect:", reason)
|
||||
return nil
|
||||
}
|
||||
|
@ -5,12 +5,10 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Tnze/go-mc/bot"
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
_ "github.com/Tnze/go-mc/data/lang/en-us"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@ -32,11 +30,9 @@ type status struct {
|
||||
}
|
||||
|
||||
func main() {
|
||||
addr, port := getAddr()
|
||||
|
||||
fmt.Printf("MCPING (%s:%d):\n", addr, port)
|
||||
|
||||
resp, delay, err := bot.PingAndList(addr, port)
|
||||
addr := getAddr()
|
||||
fmt.Printf("MCPING (%s):\n", addr)
|
||||
resp, delay, err := bot.PingAndList(addr)
|
||||
if err != nil {
|
||||
fmt.Printf("ping and list server fail: %v", err)
|
||||
os.Exit(1)
|
||||
@ -53,28 +49,14 @@ func main() {
|
||||
fmt.Println("Delay:", delay)
|
||||
}
|
||||
|
||||
func getAddr() (string, int) {
|
||||
func getAddr() string {
|
||||
const usage = "Usage: mcping <hostname>[:port]"
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("no host name.", usage)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
addr := strings.Split(os.Args[1], ":")
|
||||
var port int
|
||||
switch len(addr) {
|
||||
case 1:
|
||||
port = 25565
|
||||
case 2:
|
||||
var err error
|
||||
port, err = strconv.Atoi(addr[1])
|
||||
if err != nil {
|
||||
fmt.Println(err, usage)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
return addr[0], port
|
||||
return os.Args[1]
|
||||
}
|
||||
|
||||
func (s status) String() string {
|
||||
|
@ -19,27 +19,35 @@ const (
|
||||
|
||||
package packetid
|
||||
|
||||
// Valid PktID values.
|
||||
// Login state
|
||||
const (
|
||||
// Clientbound packets for connections in the login state.
|
||||
// Clientbound
|
||||
{{range $Name, $ID := .Login.Clientbound}} {{$Name}} = {{$ID}}
|
||||
{{end}}
|
||||
// Serverbound packets for connections in the login state
|
||||
// Serverbound
|
||||
{{range $Name, $ID := .Login.Serverbound}} {{$Name}} = {{$ID}}
|
||||
{{end}}
|
||||
// Clientbound packets for connections in the play state.
|
||||
{{range $Name, $ID := .Play.Clientbound}} {{$Name}} = {{$ID}}
|
||||
{{end}}
|
||||
// Serverbound packets for connections in the play state.
|
||||
{{range $Name, $ID := .Play.Serverbound}} {{$Name}} = {{$ID}}
|
||||
{{end}}
|
||||
// Clientbound packets used to respond to ping/status requests.
|
||||
)
|
||||
|
||||
// Ping state
|
||||
const (
|
||||
// Clientbound
|
||||
{{range $Name, $ID := .Status.Clientbound}} {{$Name}} = {{$ID}}
|
||||
{{end}}
|
||||
// Serverbound packets used to ping or read server status.
|
||||
// Serverbound
|
||||
{{range $Name, $ID := .Status.Serverbound}} {{$Name}} = {{$ID}}
|
||||
{{end}}
|
||||
)
|
||||
|
||||
// Play state
|
||||
const (
|
||||
// Clientbound
|
||||
{{range $Name, $ID := .Play.Clientbound}} {{$Name}} = {{$ID}}
|
||||
{{end}}
|
||||
// Serverbound
|
||||
{{range $Name, $ID := .Play.Serverbound}} {{$Name}} = {{$ID}}
|
||||
{{end}}
|
||||
)
|
||||
`
|
||||
)
|
||||
|
||||
|
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482
|
||||
github.com/fatih/set v0.2.1 // indirect
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/iancoleman/strcase v0.1.3
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
|
2
go.sum
2
go.sum
@ -1,5 +1,7 @@
|
||||
github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482 h1:p4g4uok3+r6Tg6fxXEQUAcMAX/WdK6WhkQW9s0jaT7k=
|
||||
github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482/go.mod h1:Cu3t5VeqE8kXjUBeNXWQprfuaP5UCIc5ggGjgMx9KFc=
|
||||
github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=
|
||||
github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/iancoleman/strcase v0.1.3 h1:dJBk1m2/qjL1twPLf68JND55vvivMupZ4wIzE8CTdBw=
|
||||
|
@ -30,7 +30,7 @@ func IsArrayTag(ty byte) bool {
|
||||
}
|
||||
|
||||
type DecoderReader = interface {
|
||||
io.ByteScanner
|
||||
io.ByteReader
|
||||
io.Reader
|
||||
}
|
||||
type Decoder struct {
|
||||
@ -42,7 +42,7 @@ func NewDecoder(r io.Reader) *Decoder {
|
||||
if br, ok := r.(DecoderReader); ok {
|
||||
d.r = br
|
||||
} else {
|
||||
d.r = bufio.NewReader(r)
|
||||
d.r = bufio.NewReaderSize(r, 0)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
15
net/conn.go
15
net/conn.go
@ -36,10 +36,7 @@ func (l Listener) Accept() (Conn, error) {
|
||||
//Conn is a minecraft Connection
|
||||
type Conn struct {
|
||||
Socket net.Conn
|
||||
Reader interface {
|
||||
io.ByteReader
|
||||
io.Reader
|
||||
}
|
||||
io.Reader
|
||||
io.Writer
|
||||
|
||||
threshold int
|
||||
@ -50,7 +47,7 @@ func DialMC(addr string) (*Conn, error) {
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
return &Conn{
|
||||
Socket: conn,
|
||||
Reader: bufio.NewReader(conn),
|
||||
Reader: conn,
|
||||
Writer: conn,
|
||||
}, err
|
||||
}
|
||||
@ -60,7 +57,7 @@ func DialMCTimeout(addr string, timeout time.Duration) (*Conn, error) {
|
||||
conn, err := net.DialTimeout("tcp", addr, timeout)
|
||||
return &Conn{
|
||||
Socket: conn,
|
||||
Reader: bufio.NewReader(conn),
|
||||
Reader: conn,
|
||||
Writer: conn,
|
||||
}, err
|
||||
}
|
||||
@ -70,7 +67,7 @@ func DialMCTimeout(addr string, timeout time.Duration) (*Conn, error) {
|
||||
func WrapConn(conn net.Conn) *Conn {
|
||||
return &Conn{
|
||||
Socket: conn,
|
||||
Reader: bufio.NewReader(conn),
|
||||
Reader: conn,
|
||||
Writer: conn,
|
||||
}
|
||||
}
|
||||
@ -91,10 +88,10 @@ func (c *Conn) WritePacket(p pk.Packet) error {
|
||||
// SetCipher load the decode/encode stream to this Conn
|
||||
func (c *Conn) SetCipher(ecoStream, decoStream cipher.Stream) {
|
||||
//加密连接
|
||||
c.Reader = bufio.NewReader(cipher.StreamReader{ //Set receiver for AES
|
||||
c.Reader = cipher.StreamReader{ //Set receiver for AES
|
||||
S: decoStream,
|
||||
R: c.Socket,
|
||||
})
|
||||
}
|
||||
c.Writer = cipher.StreamWriter{
|
||||
S: ecoStream,
|
||||
W: c.Socket,
|
||||
|
BIN
net/packet/joingame_test.bin
Normal file
BIN
net/packet/joingame_test.bin
Normal file
Binary file not shown.
@ -3,8 +3,10 @@ package packet
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Packet define a net data package
|
||||
@ -25,8 +27,9 @@ func Marshal(id int32, fields ...FieldEncoder) (pk Packet) {
|
||||
//Scan decode the packet and fill data into fields
|
||||
func (p Packet) Scan(fields ...FieldDecoder) error {
|
||||
r := bytes.NewReader(p.Data)
|
||||
rr := io.TeeReader(r, hex.Dumper(os.Stdout))
|
||||
for _, v := range fields {
|
||||
_, err := v.ReadFrom(r)
|
||||
_, err := v.ReadFrom(rr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -82,25 +85,25 @@ func (p *Packet) UnPack(r io.Reader, threshold int) error {
|
||||
if length < 1 {
|
||||
return fmt.Errorf("packet length too short")
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(p.Data[:0])
|
||||
if _, err := io.CopyN(buf, r, int64(length)); err != nil {
|
||||
buf := make([]byte, length)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return fmt.Errorf("read content of packet fail: %w", err)
|
||||
}
|
||||
buffer := bytes.NewBuffer(buf)
|
||||
|
||||
//解压数据
|
||||
if threshold > 0 {
|
||||
if err := unCompress(buf); err != nil {
|
||||
if err := unCompress(buffer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var packetID VarInt
|
||||
if _, err := packetID.ReadFrom(buf); err != nil {
|
||||
if _, err := packetID.ReadFrom(buffer); err != nil {
|
||||
return fmt.Errorf("read packet id fail: %v", err)
|
||||
}
|
||||
p.ID = int32(packetID)
|
||||
p.Data = buf.Bytes()
|
||||
p.Data = buffer.Bytes()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,14 @@
|
||||
package packet
|
||||
package packet_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"testing"
|
||||
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
var VarInts = []VarInt{0, 1, 2, 127, 128, 255, 2147483647, -1, -2147483648}
|
||||
var VarInts = []pk.VarInt{0, 1, 2, 127, 128, 255, 2147483647, -1, -2147483648}
|
||||
|
||||
var PackedVarInts = [][]byte{
|
||||
{0x00},
|
||||
@ -35,7 +38,7 @@ func TestPackVarInt(t *testing.T) {
|
||||
}
|
||||
func TestUnpackVarInt(t *testing.T) {
|
||||
for i, v := range PackedVarInts {
|
||||
var vi VarInt
|
||||
var vi pk.VarInt
|
||||
if _, err := vi.ReadFrom(bytes.NewReader(v)); err != nil {
|
||||
t.Errorf("unpack \"% x\" error: %v", v, err)
|
||||
}
|
||||
@ -46,7 +49,7 @@ func TestUnpackVarInt(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUnpackVarInt_TooLongData(t *testing.T) {
|
||||
var vi VarInt
|
||||
var vi pk.VarInt
|
||||
var data = []byte{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01}
|
||||
if _, err := vi.ReadFrom(bytes.NewReader(data)); err != nil {
|
||||
t.Logf("unpack \"% x\" error: %v", data, err)
|
||||
@ -55,7 +58,7 @@ func TestUnpackVarInt_TooLongData(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var VarLongs = []VarLong{0, 1, 2, 127, 128, 255, 2147483647, 9223372036854775807, -1, -2147483648, -9223372036854775808}
|
||||
var VarLongs = []pk.VarLong{0, 1, 2, 127, 128, 255, 2147483647, 9223372036854775807, -1, -2147483648, -9223372036854775808}
|
||||
|
||||
var PackedVarLongs = [][]byte{
|
||||
{0x00},
|
||||
@ -81,7 +84,7 @@ func TestPackVarLong(t *testing.T) {
|
||||
}
|
||||
func TestUnpackVarLong(t *testing.T) {
|
||||
for i, v := range PackedVarLongs {
|
||||
var vi VarLong
|
||||
var vi pk.VarLong
|
||||
if _, err := vi.ReadFrom(bytes.NewReader(v)); err != nil {
|
||||
t.Errorf("unpack \"% x\" error: %v", v, err)
|
||||
}
|
||||
@ -90,3 +93,46 @@ func TestUnpackVarLong(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed joingame_test.bin
|
||||
var joingame []byte
|
||||
|
||||
func TestJoinGamePacket(t *testing.T) {
|
||||
p := pk.Packet{ID: 0x24, Data: joingame}
|
||||
var (
|
||||
EID pk.Int
|
||||
Hardcore pk.Boolean
|
||||
Gamemode pk.UnsignedByte
|
||||
PreGamemode pk.Byte
|
||||
WorldCount pk.VarInt
|
||||
WorldNames = pk.Ary{Len: &WorldCount, Ary: &[]pk.String{}}
|
||||
DimensionCodec struct {
|
||||
DimensionType interface{} `nbt:"minecraft:dimension_type"`
|
||||
WorldgenBiome interface{} `nbt:"minecraft:worldgen/biome"`
|
||||
}
|
||||
Dimension interface{}
|
||||
WorldName pk.Identifier
|
||||
HashedSeed pk.Long
|
||||
MaxPlayers pk.VarInt
|
||||
ViewDistance pk.VarInt
|
||||
RDI, ERS, IsDebug, IsFlat pk.Boolean
|
||||
)
|
||||
err := p.Scan(
|
||||
&EID,
|
||||
&Hardcore,
|
||||
&Gamemode,
|
||||
&PreGamemode,
|
||||
&WorldCount,
|
||||
WorldNames,
|
||||
&pk.NBT{V: &DimensionCodec},
|
||||
&pk.NBT{V: &Dimension},
|
||||
&WorldName,
|
||||
&HashedSeed,
|
||||
&MaxPlayers,
|
||||
&ViewDistance,
|
||||
&RDI, &ERS, &IsDebug, &IsFlat,
|
||||
)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,13 @@ import (
|
||||
)
|
||||
|
||||
type Ary struct {
|
||||
Len Field // One of VarInt, VarLong, Int or Long
|
||||
Ary interface{} // FieldEncoder, FieldDecoder or both (Field)
|
||||
Len Field // Pointer of VarInt, VarLong, Int or Long
|
||||
Ary interface{} // Slice of FieldEncoder, FieldDecoder or both (Field)
|
||||
}
|
||||
|
||||
func (a Ary) WriteTo(r io.Writer) (n int64, err error) {
|
||||
length := int(reflect.ValueOf(a.Len).Int())
|
||||
array := reflect.ValueOf(a.Ary).Elem()
|
||||
array := reflect.ValueOf(a.Ary)
|
||||
for i := 0; i < length; i++ {
|
||||
elem := array.Index(i)
|
||||
nn, err := elem.Interface().(FieldEncoder).WriteTo(r)
|
||||
@ -25,11 +25,15 @@ func (a Ary) WriteTo(r io.Writer) (n int64, err error) {
|
||||
}
|
||||
|
||||
func (a Ary) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
length := int(reflect.ValueOf(a.Len).Int())
|
||||
length := int(reflect.ValueOf(a.Len).Elem().Int())
|
||||
array := reflect.ValueOf(a.Ary).Elem()
|
||||
if array.Cap() < length {
|
||||
array = reflect.MakeSlice(array.Type(), length, length)
|
||||
a.Ary = array.Interface()
|
||||
}
|
||||
for i := 0; i < length; i++ {
|
||||
elem := array.Index(i)
|
||||
nn, err := elem.Interface().(FieldDecoder).ReadFrom(r)
|
||||
nn, err := elem.Addr().Interface().(FieldDecoder).ReadFrom(r)
|
||||
n += nn
|
||||
if err != nil {
|
||||
return n, err
|
||||
|
@ -1,82 +0,0 @@
|
||||
// Package ptypes implements encoding and decoding for high-level packets.
|
||||
package ptypes
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/Tnze/go-mc/bot/world/entity"
|
||||
"github.com/Tnze/go-mc/nbt"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
// ChunkData is a client-bound packet which describes a chunk.
|
||||
type ChunkData struct {
|
||||
X, Z pk.Int
|
||||
FullChunk pk.Boolean
|
||||
PrimaryBitMask pk.VarInt
|
||||
Heightmaps struct{}
|
||||
Biomes biomesData
|
||||
Data pk.ByteArray
|
||||
BlockEntities blockEntities
|
||||
}
|
||||
|
||||
func (p *ChunkData) ReadFrom(r io.Reader) (int64, error) {
|
||||
return pk.Tuple{
|
||||
&p.X,
|
||||
&p.Z,
|
||||
&p.FullChunk,
|
||||
&p.PrimaryBitMask,
|
||||
&pk.NBT{V: &p.Heightmaps},
|
||||
pk.Opt{Has: &p.FullChunk, Field: &p.Biomes},
|
||||
&p.Data,
|
||||
&p.BlockEntities,
|
||||
}.ReadFrom(r)
|
||||
}
|
||||
|
||||
type biomesData struct {
|
||||
data []pk.VarInt
|
||||
}
|
||||
|
||||
func (b *biomesData) ReadFrom(r io.Reader) (int64, error) {
|
||||
var n pk.VarInt // Number of Biomes Data
|
||||
return pk.Tuple{
|
||||
&n, pk.Ary{Len: &n, Ary: []pk.VarInt{}},
|
||||
}.ReadFrom(r)
|
||||
}
|
||||
|
||||
type blockEntities []entity.BlockEntity
|
||||
|
||||
// Decode implement net.packet.FieldDecoder
|
||||
func (b *blockEntities) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
var sz pk.VarInt // Number of BlockEntities
|
||||
if nn, err := sz.ReadFrom(r); err != nil {
|
||||
return nn, err
|
||||
} else {
|
||||
n += nn
|
||||
}
|
||||
*b = make(blockEntities, sz)
|
||||
lr := &io.LimitedReader{R: r, N: math.MaxInt64}
|
||||
d := nbt.NewDecoder(lr)
|
||||
for i := 0; i < int(sz); i++ {
|
||||
if err := d.Decode(&(*b)[i]); err != nil {
|
||||
return math.MaxInt64 - lr.N, err
|
||||
}
|
||||
}
|
||||
return math.MaxInt64 - lr.N, nil
|
||||
}
|
||||
|
||||
// TileEntityData describes a change to a tile entity.
|
||||
type TileEntityData struct {
|
||||
Pos pk.Position
|
||||
Action pk.UnsignedByte
|
||||
Data entity.BlockEntity
|
||||
}
|
||||
|
||||
func (p *TileEntityData) ReadFrom(r io.Reader) (int64, error) {
|
||||
return pk.Tuple{
|
||||
&p.Pos,
|
||||
&p.Action,
|
||||
&pk.NBT{V: &p.Data},
|
||||
}.ReadFrom(r)
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
package ptypes
|
||||
|
||||
import pk "github.com/Tnze/go-mc/net/packet"
|
||||
|
||||
// SpawnEntity is a client-bound packet used to spawn a non-mob entity.
|
||||
type SpawnEntity struct {
|
||||
ID pk.VarInt
|
||||
UUID pk.UUID
|
||||
Type pk.VarInt
|
||||
X, Y, Z pk.Double
|
||||
Pitch, Yaw pk.Angle
|
||||
Data pk.Int
|
||||
VelX, VelY, VelZ pk.Short
|
||||
}
|
||||
|
||||
func (p *SpawnEntity) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.ID, &p.UUID, &p.Type,
|
||||
&p.X, &p.Y, &p.Z, &p.Pitch, &p.Yaw,
|
||||
&p.Data, &p.VelX, &p.VelY, &p.VelZ)
|
||||
}
|
||||
|
||||
// SpawnPlayer is a client-bound packet used to describe a player entering
|
||||
// visible range.
|
||||
type SpawnPlayer struct {
|
||||
ID pk.VarInt
|
||||
UUID pk.UUID
|
||||
X, Y, Z pk.Double
|
||||
Yaw, Pitch pk.Angle
|
||||
}
|
||||
|
||||
func (p *SpawnPlayer) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.ID, &p.UUID, &p.X, &p.Y, &p.Z, &p.Yaw, &p.Pitch)
|
||||
}
|
||||
|
||||
// SpawnLivingEntity is a client-bound packet used to spawn a mob.
|
||||
type SpawnLivingEntity struct {
|
||||
ID pk.VarInt
|
||||
UUID pk.UUID
|
||||
Type pk.VarInt
|
||||
X, Y, Z pk.Double
|
||||
Yaw, Pitch pk.Angle
|
||||
HeadPitch pk.Angle
|
||||
VelX, VelY, VelZ pk.Short
|
||||
}
|
||||
|
||||
func (p *SpawnLivingEntity) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.ID, &p.UUID, &p.Type,
|
||||
&p.X, &p.Y, &p.Z, &p.Yaw, &p.Pitch,
|
||||
&p.HeadPitch, &p.VelX, &p.VelY, &p.VelZ)
|
||||
}
|
||||
|
||||
// EntityAnimationClientbound updates the animation state of an entity.
|
||||
type EntityAnimationClientbound struct {
|
||||
ID pk.VarInt
|
||||
Animation pk.UnsignedByte
|
||||
}
|
||||
|
||||
func (p *EntityAnimationClientbound) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.ID, &p.Animation)
|
||||
}
|
||||
|
||||
// EntityPosition is a clientbound packet used to update an entities position.
|
||||
type EntityPosition struct {
|
||||
ID pk.VarInt
|
||||
X, Y, Z pk.Short // Deltas
|
||||
OnGround pk.Boolean
|
||||
}
|
||||
|
||||
func (p *EntityPosition) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.ID, &p.X, &p.Y, &p.Z, &p.OnGround)
|
||||
}
|
||||
|
||||
// EntityPosition is a clientbound packet used to update an entities position
|
||||
// and its rotation.
|
||||
type EntityPositionLook struct {
|
||||
ID pk.VarInt
|
||||
X, Y, Z pk.Short // Deltas
|
||||
Yaw, Pitch pk.Angle
|
||||
OnGround pk.Boolean
|
||||
}
|
||||
|
||||
func (p *EntityPositionLook) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.ID, &p.X, &p.Y, &p.Z, &p.Yaw, &p.Pitch, &p.OnGround)
|
||||
}
|
||||
|
||||
// EntityRotation is a clientbound packet used to update an entities rotation.
|
||||
type EntityRotation struct {
|
||||
ID pk.VarInt
|
||||
Yaw, Pitch pk.Angle
|
||||
OnGround pk.Boolean
|
||||
}
|
||||
|
||||
func (p *EntityRotation) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.ID, &p.Yaw, &p.Pitch, &p.OnGround)
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
// Package ptypes implements encoding and decoding for high-level packets.
|
||||
package ptypes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/Tnze/go-mc/bot/world/entity"
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
"github.com/Tnze/go-mc/nbt"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
// SetSlot is a clientbound packet which configures an inventory slot.
|
||||
// A window ID of -1 represents the cursor, and a window ID of 0 represents
|
||||
// the players inventory.
|
||||
type SetSlot struct {
|
||||
WindowID pk.Byte
|
||||
Slot pk.Short
|
||||
SlotData entity.Slot
|
||||
}
|
||||
|
||||
func (p *SetSlot) Decode(pkt pk.Packet) error {
|
||||
if err := pkt.Scan(&p.WindowID, &p.Slot, &p.SlotData); err != nil && !errors.Is(err, nbt.ErrEND) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WindowItems is a clientbound packet describing the contents of multiple
|
||||
// inventory slots in a window/inventory.
|
||||
type WindowItems struct {
|
||||
WindowID pk.Byte
|
||||
Slots []entity.Slot
|
||||
}
|
||||
|
||||
func (p *WindowItems) ReadFrom(r io.Reader) (int64, error) {
|
||||
var count pk.Short
|
||||
return pk.Tuple{
|
||||
&p.WindowID,
|
||||
&count,
|
||||
&pk.Ary{
|
||||
Len: &count,
|
||||
Ary: []entity.Slot{},
|
||||
},
|
||||
}.ReadFrom(r)
|
||||
}
|
||||
|
||||
// OpenWindow is a clientbound packet which opens an inventory.
|
||||
type OpenWindow struct {
|
||||
WindowID pk.VarInt
|
||||
WindowType pk.VarInt
|
||||
Title chat.Message
|
||||
}
|
||||
|
||||
func (p *OpenWindow) Decode(pkt pk.Packet) error {
|
||||
if err := pkt.Scan(&p.WindowID, &p.WindowType, &p.Title); err != nil && !errors.Is(err, nbt.ErrEND) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ConfirmTransaction struct {
|
||||
WindowID pk.Byte
|
||||
ActionID pk.Short
|
||||
Accepted pk.Boolean
|
||||
}
|
||||
|
||||
func (p *ConfirmTransaction) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.WindowID, &p.ActionID, &p.Accepted)
|
||||
}
|
||||
|
||||
func (p ConfirmTransaction) Encode() pk.Packet {
|
||||
return pk.Marshal(
|
||||
packetid.TransactionServerbound,
|
||||
p.WindowID,
|
||||
p.ActionID,
|
||||
p.Accepted,
|
||||
)
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
package ptypes
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
// SoundEffect is a client-bound packet used to play a specific sound ID
|
||||
// on the client.
|
||||
type SoundEffect struct {
|
||||
Sound pk.VarInt
|
||||
Category pk.VarInt
|
||||
X, Y, Z pk.Int
|
||||
Volume, Pitch pk.Float
|
||||
}
|
||||
|
||||
func (p *SoundEffect) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.Sound, &p.Category, &p.X, &p.Y, &p.Z, &p.Volume, &p.Pitch)
|
||||
}
|
||||
|
||||
// NamedSoundEffect is a client-bound packet used to play a sound with the
|
||||
// specified name on the client.
|
||||
type NamedSoundEffect struct {
|
||||
Sound pk.String
|
||||
Category pk.VarInt
|
||||
X, Y, Z pk.Int
|
||||
Volume, Pitch pk.Float
|
||||
}
|
||||
|
||||
func (p *NamedSoundEffect) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.Sound, &p.Category, &p.X, &p.Y, &p.Z, &p.Volume, &p.Pitch)
|
||||
}
|
||||
|
||||
// ChatMessageClientbound represents a chat message forwarded by the server.
|
||||
type ChatMessageClientbound struct {
|
||||
S chat.Message
|
||||
Pos pk.Byte
|
||||
Sender pk.UUID
|
||||
}
|
||||
|
||||
func (p *ChatMessageClientbound) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.S, &p.Pos, &p.Sender)
|
||||
}
|
||||
|
||||
// UpdateHealth encodes player health/food information from the server.
|
||||
type UpdateHealth struct {
|
||||
Health pk.Float
|
||||
Food pk.VarInt
|
||||
FoodSaturation pk.Float
|
||||
}
|
||||
|
||||
func (p *UpdateHealth) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.Health, &p.Food, &p.FoodSaturation)
|
||||
}
|
||||
|
||||
// PluginData encodes the custom data encoded in a plugin message.
|
||||
type PluginData []byte
|
||||
|
||||
func (p PluginData) WriteTo(w io.Writer) (int64, error) {
|
||||
n, err := w.Write(p)
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
func (p *PluginData) ReadFrom(r io.Reader) (int64, error) {
|
||||
d, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return int64(len(d)), err
|
||||
}
|
||||
*p = d
|
||||
return int64(len(d)), nil
|
||||
}
|
||||
|
||||
// PluginMessage represents a packet with a customized payload.
|
||||
type PluginMessage struct {
|
||||
Channel pk.Identifier
|
||||
Data PluginData
|
||||
}
|
||||
|
||||
func (p *PluginMessage) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.Channel, &p.Data)
|
||||
}
|
||||
|
||||
func (p *PluginMessage) Encode() pk.Packet {
|
||||
return pk.Marshal(
|
||||
packetid.CustomPayloadServerbound,
|
||||
p.Channel,
|
||||
p.Data,
|
||||
)
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
// Package ptypes implements encoding and decoding for high-level packets.
|
||||
package ptypes
|
||||
|
||||
import (
|
||||
"github.com/Tnze/go-mc/data/packetid"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
// PositionAndLookClientbound describes the location and orientation of
|
||||
// the player.
|
||||
type PositionAndLookClientbound struct {
|
||||
X, Y, Z pk.Double
|
||||
Yaw, Pitch pk.Float
|
||||
Flags pk.Byte
|
||||
TeleportID pk.VarInt
|
||||
}
|
||||
|
||||
func (p *PositionAndLookClientbound) RelativeX() bool {
|
||||
return p.Flags&0x01 != 0
|
||||
}
|
||||
func (p *PositionAndLookClientbound) RelativeY() bool {
|
||||
return p.Flags&0x02 != 0
|
||||
}
|
||||
func (p *PositionAndLookClientbound) RelativeZ() bool {
|
||||
return p.Flags&0x04 != 0
|
||||
}
|
||||
func (p *PositionAndLookClientbound) RelativeYaw() bool {
|
||||
return p.Flags&0x08 != 0
|
||||
}
|
||||
func (p *PositionAndLookClientbound) RelativePitch() bool {
|
||||
return p.Flags&0x10 != 0
|
||||
}
|
||||
|
||||
func (p *PositionAndLookClientbound) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.X, &p.Y, &p.Z, &p.Yaw, &p.Pitch, &p.Flags, &p.TeleportID)
|
||||
}
|
||||
|
||||
// PositionAndLookServerbound describes the location and orientation of
|
||||
// the player.
|
||||
type PositionAndLookServerbound struct {
|
||||
X, Y, Z pk.Double
|
||||
Yaw, Pitch pk.Float
|
||||
OnGround pk.Boolean
|
||||
}
|
||||
|
||||
func (p PositionAndLookServerbound) Encode() pk.Packet {
|
||||
return pk.Marshal(
|
||||
packetid.PositionLook,
|
||||
p.X, p.Y, p.Z,
|
||||
p.Yaw, p.Pitch,
|
||||
p.OnGround,
|
||||
)
|
||||
}
|
||||
|
||||
// Position describes the position of the player.
|
||||
type Position struct {
|
||||
X, Y, Z pk.Double
|
||||
OnGround pk.Boolean
|
||||
}
|
||||
|
||||
func (p Position) Encode() pk.Packet {
|
||||
return pk.Marshal(
|
||||
packetid.PositionServerbound,
|
||||
p.X, p.Y, p.Z,
|
||||
p.OnGround,
|
||||
)
|
||||
}
|
||||
|
||||
// Look describes the rotation of the player.
|
||||
type Look struct {
|
||||
Yaw, Pitch pk.Float
|
||||
OnGround pk.Boolean
|
||||
}
|
||||
|
||||
func (p Look) Encode() pk.Packet {
|
||||
return pk.Marshal(
|
||||
packetid.Look,
|
||||
p.Yaw, p.Pitch,
|
||||
p.OnGround,
|
||||
)
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package ptypes
|
||||
|
||||
import (
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
// JoinGame encodes global/world information from the server.
|
||||
type JoinGame struct {
|
||||
PlayerEntity pk.Int
|
||||
Hardcore pk.Boolean
|
||||
Gamemode pk.UnsignedByte
|
||||
PrevGamemode pk.UnsignedByte
|
||||
WorldCount pk.VarInt
|
||||
WorldNames pk.Identifier
|
||||
//DimensionCodec pk.NBT
|
||||
Dimension pk.Int
|
||||
WorldName pk.Identifier
|
||||
HashedSeed pk.Long
|
||||
maxPlayers pk.VarInt // Now ignored
|
||||
ViewDistance pk.VarInt
|
||||
RDI pk.Boolean // Reduced Debug Info
|
||||
ERS pk.Boolean // Enable respawn screen
|
||||
IsDebug pk.Boolean
|
||||
IsFlat pk.Boolean
|
||||
}
|
||||
|
||||
func (p *JoinGame) Decode(pkt pk.Packet) error {
|
||||
return pkt.Scan(&p.PlayerEntity, &p.Hardcore, &p.Gamemode, &p.PrevGamemode,
|
||||
&p.WorldCount, &p.WorldNames, &p.Dimension,
|
||||
&p.WorldName, &p.HashedSeed, &p.maxPlayers, &p.ViewDistance,
|
||||
&p.RDI, &p.ERS, &p.IsDebug, &p.IsFlat)
|
||||
}
|
Reference in New Issue
Block a user