@ -1,6 +1,6 @@
|
|||||||
# Go-MC
|
# Go-MC
|
||||||

|

|
||||||

|

|
||||||
[](https://godoc.org/github.com/Tnze/go-mc)
|
[](https://godoc.org/github.com/Tnze/go-mc)
|
||||||
[](https://goreportcard.com/report/github.com/Tnze/go-mc)
|
[](https://goreportcard.com/report/github.com/Tnze/go-mc)
|
||||||
[](https://travis-ci.org/Tnze/go-mc)
|
[](https://travis-ci.org/Tnze/go-mc)
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
package bot
|
package bot
|
||||||
|
|
||||||
import (
|
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"
|
||||||
"github.com/Tnze/go-mc/bot/world/entity"
|
"github.com/Tnze/go-mc/bot/world/entity"
|
||||||
"github.com/Tnze/go-mc/bot/world/entity/player"
|
"github.com/Tnze/go-mc/bot/world/entity/player"
|
||||||
|
"github.com/Tnze/go-mc/data"
|
||||||
"github.com/Tnze/go-mc/net"
|
"github.com/Tnze/go-mc/net"
|
||||||
|
"github.com/Tnze/go-mc/net/packet"
|
||||||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is used to access Minecraft server
|
// Client is used to access Minecraft server
|
||||||
@ -14,14 +22,35 @@ type Client struct {
|
|||||||
|
|
||||||
player.Player
|
player.Player
|
||||||
PlayInfo
|
PlayInfo
|
||||||
|
ServInfo
|
||||||
abilities PlayerAbilities
|
abilities PlayerAbilities
|
||||||
settings Settings
|
settings Settings
|
||||||
|
|
||||||
Wd world.World //the map data
|
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.
|
// Delegate allows you push a function to let HandleGame run.
|
||||||
// Do not send at the same goroutine!
|
// Do not send at the same goroutine!
|
||||||
Delegate chan func() error
|
Delegate chan func() error
|
||||||
Events eventBroker
|
Events eventBroker
|
||||||
|
|
||||||
|
closing chan struct{}
|
||||||
|
inbound chan pk.Packet
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
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(data.CloseWindowServerbound, pk.UnsignedByte(windowID)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient init and return a new Client.
|
// NewClient init and return a new Client.
|
||||||
@ -31,20 +60,18 @@ type Client struct {
|
|||||||
//
|
//
|
||||||
// For online-mode, you need login your Mojang account
|
// For online-mode, you need login your Mojang account
|
||||||
// and load your Name, UUID and AccessToken to client.
|
// and load your Name, UUID and AccessToken to client.
|
||||||
func NewClient() (c *Client) {
|
func NewClient() *Client {
|
||||||
c = new(Client)
|
return &Client{
|
||||||
|
settings: DefaultSettings,
|
||||||
//init Client
|
Auth: Auth{Name: "Steve"},
|
||||||
c.settings = DefaultSettings
|
Delegate: make(chan func() error),
|
||||||
c.Name = "Steve"
|
Wd: world.World{
|
||||||
c.Delegate = make(chan func() error)
|
Entities: make(map[int32]*entity.Entity, 8192),
|
||||||
|
Chunks: make(map[world.ChunkLoc]*world.Chunk, 2048),
|
||||||
c.Wd = world.World{
|
},
|
||||||
Entities: make(map[int32]entity.Entity),
|
closing: make(chan struct{}),
|
||||||
Chunks: make(map[world.ChunkLoc]*world.Chunk),
|
inbound: make(chan pk.Packet, 5),
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//PlayInfo content player info in server.
|
//PlayInfo content player info in server.
|
||||||
@ -58,7 +85,12 @@ type PlayInfo struct {
|
|||||||
WorldName string //当前世界的名字
|
WorldName string //当前世界的名字
|
||||||
IsDebug bool //调试
|
IsDebug bool //调试
|
||||||
IsFlat bool //超平坦世界
|
IsFlat bool //超平坦世界
|
||||||
// SpawnPosition Position //主世界出生点
|
SpawnPosition Position //主世界出生点
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServInfo contains information about the server implementation.
|
||||||
|
type ServInfo struct {
|
||||||
|
Brand string
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlayerAbilities defines what player can do.
|
// PlayerAbilities defines what player can do.
|
||||||
|
59
bot/event.go
59
bot/event.go
@ -2,13 +2,38 @@ package bot
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Tnze/go-mc/bot/world/entity"
|
"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/chat"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
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 {
|
type eventBroker struct {
|
||||||
|
seenPackets seenPacketFlags
|
||||||
|
isReady bool
|
||||||
|
|
||||||
GameStart func() error
|
GameStart func() error
|
||||||
ChatMsg func(msg chat.Message, pos byte, sender uuid.UUID) error
|
ChatMsg func(msg chat.Message, pos byte, sender uuid.UUID) error
|
||||||
Disconnect func(reason chat.Message) error
|
Disconnect func(reason chat.Message) error
|
||||||
@ -17,6 +42,7 @@ type eventBroker struct {
|
|||||||
SoundPlay func(name string, category int, x, y, z float64, volume, pitch float32) error
|
SoundPlay func(name string, category int, x, y, z float64, volume, pitch float32) error
|
||||||
PluginMessage func(channel string, data []byte) error
|
PluginMessage func(channel string, data []byte) error
|
||||||
HeldItemChange func(slot int) error
|
HeldItemChange func(slot int) error
|
||||||
|
OpenWindow func(pkt ptypes.OpenWindow) error
|
||||||
|
|
||||||
// ExperienceChange will be called every time player's experience level updates.
|
// ExperienceChange will be called every time player's experience level updates.
|
||||||
// Parameters:
|
// Parameters:
|
||||||
@ -27,8 +53,37 @@ type eventBroker struct {
|
|||||||
|
|
||||||
WindowsItem func(id byte, slots []entity.Slot) error
|
WindowsItem func(id byte, slots []entity.Slot) error
|
||||||
WindowsItemChange func(id byte, slotID int, slot entity.Slot) error
|
WindowsItemChange func(id byte, slotID int, slot entity.Slot) error
|
||||||
|
WindowConfirmation func(pkt ptypes.ConfirmTransaction) error
|
||||||
|
|
||||||
// ReceivePacket will be called when new packet arrive.
|
// ServerDifficultyChange is called whenever the gamemode of the server changes.
|
||||||
// Default handler will run only if pass == false.
|
// 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)
|
ReceivePacket func(p pk.Packet) (pass bool, err error)
|
||||||
|
|
||||||
|
// PrePhysics will be called before a phyiscs tick.
|
||||||
|
PrePhysics func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
934
bot/ingame.go
934
bot/ingame.go
File diff suppressed because it is too large
Load Diff
15
bot/mcbot.go
15
bot/mcbot.go
@ -9,12 +9,13 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/data"
|
||||||
mcnet "github.com/Tnze/go-mc/net"
|
mcnet "github.com/Tnze/go-mc/net"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProtocolVersion , the protocol version number of minecraft net protocol
|
// ProtocolVersion , the protocol version number of minecraft net protocol
|
||||||
const ProtocolVersion = 751
|
const ProtocolVersion = 753
|
||||||
|
|
||||||
// JoinServer connect a Minecraft server for playing the game.
|
// JoinServer connect a Minecraft server for playing the game.
|
||||||
func (c *Client) JoinServer(addr string, port int) (err error) {
|
func (c *Client) JoinServer(addr string, port int) (err error) {
|
||||||
@ -98,7 +99,7 @@ func (c *Client) join(d Dialer, addr string) (err error) {
|
|||||||
case 0x02: //Login Success
|
case 0x02: //Login Success
|
||||||
// uuid, l := pk.UnpackString(pack.Data)
|
// uuid, l := pk.UnpackString(pack.Data)
|
||||||
// name, _ := unpackString(pack.Data[l:])
|
// name, _ := unpackString(pack.Data[l:])
|
||||||
return //switches the connection state to PLAY.
|
return nil
|
||||||
case 0x03: //Set Compression
|
case 0x03: //Set Compression
|
||||||
var threshold pk.VarInt
|
var threshold pk.VarInt
|
||||||
if err := pack.Scan(&threshold); err != nil {
|
if err := pack.Scan(&threshold); err != nil {
|
||||||
@ -124,3 +125,13 @@ type Dialer interface {
|
|||||||
func (c *Client) Conn() *mcnet.Conn {
|
func (c *Client) Conn() *mcnet.Conn {
|
||||||
return c.conn
|
return c.conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendMessage sends a chat message.
|
||||||
|
func (c *Client) SendMessage(msg string) error {
|
||||||
|
return c.conn.WritePacket(
|
||||||
|
pk.Marshal(
|
||||||
|
data.ChatServerbound,
|
||||||
|
pk.String(msg),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Tnze/go-mc/data"
|
"github.com/Tnze/go-mc/data"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
"github.com/Tnze/go-mc/net/ptypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SwingArm swing player's arm.
|
// SwingArm swing player's arm.
|
||||||
@ -13,7 +14,7 @@ import (
|
|||||||
// It's just animation.
|
// It's just animation.
|
||||||
func (c *Client) SwingArm(hand int) error {
|
func (c *Client) SwingArm(hand int) error {
|
||||||
return c.conn.WritePacket(pk.Marshal(
|
return c.conn.WritePacket(pk.Marshal(
|
||||||
data.AnimationServerbound,
|
data.ArmAnimation,
|
||||||
pk.VarInt(hand),
|
pk.VarInt(hand),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -21,7 +22,7 @@ func (c *Client) SwingArm(hand int) error {
|
|||||||
// Respawn the player when it was dead.
|
// Respawn the player when it was dead.
|
||||||
func (c *Client) Respawn() error {
|
func (c *Client) Respawn() error {
|
||||||
return c.conn.WritePacket(pk.Marshal(
|
return c.conn.WritePacket(pk.Marshal(
|
||||||
data.ClientStatus,
|
data.ClientCommand,
|
||||||
pk.VarInt(0),
|
pk.VarInt(0),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -77,18 +78,17 @@ func (c *Client) Chat(msg string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return c.conn.WritePacket(pk.Marshal(
|
return c.conn.WritePacket(pk.Marshal(
|
||||||
data.ChatMessageServerbound,
|
data.ChatServerbound,
|
||||||
pk.String(msg),
|
pk.String(msg),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginMessage is used by mods and plugins to send their data.
|
// PluginMessage is used by mods and plugins to send their data.
|
||||||
func (c *Client) PluginMessage(channal string, msg []byte) error {
|
func (c *Client) PluginMessage(channel string, msg []byte) error {
|
||||||
return c.conn.WritePacket(pk.Marshal(
|
return c.conn.WritePacket((&ptypes.PluginMessage{
|
||||||
data.PluginMessageServerbound,
|
Channel: pk.Identifier(channel),
|
||||||
pk.Identifier(channal),
|
Data: ptypes.PluginData(msg),
|
||||||
pluginMessageData(msg),
|
}).Encode())
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseBlock is used to place or use a block.
|
// UseBlock is used to place or use a block.
|
||||||
@ -103,7 +103,7 @@ func (c *Client) PluginMessage(channal string, msg []byte) error {
|
|||||||
// insideBlock is true when the player's head is inside of a block's collision.
|
// 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 {
|
func (c *Client) UseBlock(hand, locX, locY, locZ, face int, cursorX, cursorY, cursorZ float32, insideBlock bool) error {
|
||||||
return c.conn.WritePacket(pk.Marshal(
|
return c.conn.WritePacket(pk.Marshal(
|
||||||
data.PlayerBlockPlacement,
|
data.UseItem,
|
||||||
pk.VarInt(hand),
|
pk.VarInt(hand),
|
||||||
pk.Position{X: locX, Y: locY, Z: locZ},
|
pk.Position{X: locX, Y: locY, Z: locZ},
|
||||||
pk.VarInt(face),
|
pk.VarInt(face),
|
||||||
@ -120,7 +120,7 @@ func (c *Client) SelectItem(slot int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return c.conn.WritePacket(pk.Marshal(
|
return c.conn.WritePacket(pk.Marshal(
|
||||||
data.HeldItemChangeServerbound,
|
data.HeldItemSlotServerbound,
|
||||||
pk.Short(slot),
|
pk.Short(slot),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -144,7 +144,7 @@ func (c *Client) PickItem(slot int) error {
|
|||||||
|
|
||||||
func (c *Client) playerAction(status, locX, locY, locZ, face int) error {
|
func (c *Client) playerAction(status, locX, locY, locZ, face int) error {
|
||||||
return c.conn.WritePacket(pk.Marshal(
|
return c.conn.WritePacket(pk.Marshal(
|
||||||
data.PlayerDigging,
|
data.BlockDig,
|
||||||
pk.VarInt(status),
|
pk.VarInt(status),
|
||||||
pk.Position{X: locX, Y: locY, Z: locZ},
|
pk.Position{X: locX, Y: locY, Z: locZ},
|
||||||
pk.Byte(face),
|
pk.Byte(face),
|
||||||
@ -180,7 +180,7 @@ func (c *Client) SwapItem() error {
|
|||||||
|
|
||||||
// Disconnect disconnect the server.
|
// Disconnect disconnect the server.
|
||||||
// Server will close the connection.
|
// Server will close the connection.
|
||||||
func (c *Client) Disconnect() error {
|
func (c *Client) disconnect() error {
|
||||||
return c.conn.Close()
|
return c.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
118
bot/path/blocks.go
Normal file
118
bot/path/blocks.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
10
bot/path/inputs.go
Normal file
10
bot/path/inputs.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package path
|
||||||
|
|
||||||
|
// Inputs describes the desired movements of the player.
|
||||||
|
type Inputs struct {
|
||||||
|
Yaw, Pitch float64
|
||||||
|
|
||||||
|
ThrottleX, ThrottleZ float64
|
||||||
|
|
||||||
|
Jump bool
|
||||||
|
}
|
358
bot/path/movement.go
Normal file
358
bot/path/movement.go
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// fmt.Printf("%s.Possible(%d,%d,%d)\n", m, x, y, z)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
196
bot/path/path.go
Normal file
196
bot/path/path.go
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
// Package path implements pathfinding.
|
||||||
|
package path
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
"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
|
||||||
|
|
||||||
|
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)
|
||||||
|
// fmt.Printf("%v-%v: Trying (%v) %v: possible=%v\n", t.Movement, t.Pos, pos, m, possible)
|
||||||
|
if possible {
|
||||||
|
possibles = append(possibles, Tile{
|
||||||
|
Nav: t.Nav,
|
||||||
|
Movement: m,
|
||||||
|
Pos: pos,
|
||||||
|
BlockStatus: t.Nav.World.GetBlockStatus(pos.X, pos.Y, pos.Z),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Printf("%v.Neighbours(): %+v\n", t.Pos, possibles)
|
||||||
|
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 = strings.HasSuffix(b.Name, "_stairs")
|
||||||
|
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
|
||||||
|
|
||||||
|
// Special logic for stairs: Try to go towards the downwards edge initially.
|
||||||
|
if isStairs && 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
return (d.X*d.X+d.Z*d.Z) < (0.18*0.18) && d.Y >= -0.065 && d.Y <= 0.08
|
||||||
|
}
|
143
bot/phy/aabb.go
Normal file
143
bot/phy/aabb.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
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
|
||||||
|
}
|
300
bot/phy/phy.go
Normal file
300
bot/phy/phy.go
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
// Package phy implements a minimal physics simulation necessary for realistic
|
||||||
|
// bot behavior.
|
||||||
|
package phy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"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 {
|
||||||
|
fmt.Printf("TELEPORT (y=%0.2f, velY=%0.3f): %0.2f, %0.2f, %0.2f\n", s.Pos.Y, s.Vel.Y, player.X-s.Pos.X, player.Y-s.Pos.Y, player.Z-s.Pos.Z)
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// fmt.Println(input.Jump, s.lastJump, s.onGround)
|
||||||
|
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) {
|
||||||
|
// fmt.Printf("TICK POSITION: %0.2f, %0.2f, %0.2f - (%0.2f, %0.2f, %0.2f)\n", s.Pos.X, s.Pos.Y, s.Pos.Z, s.Vel.X, s.Vel.Y, s.Vel.Z)
|
||||||
|
|
||||||
|
player, newVel := s.computeCollisionYXZ(s.BB(), s.BB().Offset(s.Vel.X, s.Vel.Y, s.Vel.Z), s.Vel, w)
|
||||||
|
//fmt.Printf("offset = %0.2f, %0.2f, %0.2f\n", player.X.Min-s.Pos.X, player.Y.Min-s.Pos.Y, player.Z.Min-s.Pos.Z)
|
||||||
|
|
||||||
|
//fmt.Printf("onGround = %v, s.Vel.Y = %0.3f, newVel.Y = %0.3f\n", s.onGround, s.Vel.Y, newVel.Y)
|
||||||
|
if s.onGround || (s.Vel.Y != newVel.Y && s.Vel.Y < 0) {
|
||||||
|
bb := s.BB()
|
||||||
|
//fmt.Printf("Player pos = %0.2f, %0.2f, %0.2f\n", bb.X.Min, bb.Y.Min, bb.Z.Min)
|
||||||
|
surroundings := s.surroundings(bb.Offset(s.Vel.X, stepHeight, s.Vel.Y), 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)
|
||||||
|
//fmt.Printf("Post-collision = %0.2f, %0.2f, %0.2f\n", bb.X.Min, bb.Y.Min, bb.Z.Min)
|
||||||
|
|
||||||
|
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)
|
||||||
|
//fmt.Printf("Post-lower = %0.2f, %0.2f, %0.2f\n", bb.X.Min, bb.Y.Min, bb.Z.Min)
|
||||||
|
|
||||||
|
oldMove := newVel.X*newVel.X + newVel.Z*newVel.Z
|
||||||
|
newMove := outVel.X*outVel.X + outVel.Z*outVel.Z
|
||||||
|
// fmt.Printf("oldMove = %0.2f, newMove = %0.2f\n", oldMove*1000, newMove*1000)
|
||||||
|
if oldMove >= newMove || outVel.Y <= (0.000002-stepHeight) {
|
||||||
|
// fmt.Println("nope")
|
||||||
|
} else {
|
||||||
|
player = bb
|
||||||
|
newVel = outVel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update flags.
|
||||||
|
s.Pos.X = player.X.Min + playerWidth/2
|
||||||
|
s.Pos.Y = player.Y.Min
|
||||||
|
s.Pos.Z = player.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
|
||||||
|
}
|
||||||
|
// fmt.Printf("(%.2f - %.2f) = %.2f\n", new, old, delta)
|
||||||
|
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
|
||||||
|
}
|
@ -9,6 +9,7 @@ type Settings struct {
|
|||||||
DisplayedSkinParts uint8 //皮肤显示
|
DisplayedSkinParts uint8 //皮肤显示
|
||||||
MainHand int //主手
|
MainHand int //主手
|
||||||
ReceiveMap bool //接收地图数据
|
ReceiveMap bool //接收地图数据
|
||||||
|
Brand string // The brand string presented to the server.
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -33,4 +34,5 @@ var DefaultSettings = Settings{
|
|||||||
DisplayedSkinParts: Jacket | LeftSleeve | RightSleeve | LeftPantsLeg | RightPantsLeg | Hat,
|
DisplayedSkinParts: Jacket | LeftSleeve | RightSleeve | LeftPantsLeg | RightPantsLeg | Hat,
|
||||||
MainHand: 1,
|
MainHand: 1,
|
||||||
ReceiveMap: true,
|
ReceiveMap: true,
|
||||||
|
Brand: "vanilla",
|
||||||
}
|
}
|
||||||
|
38
bot/world/bitarray.go
Normal file
38
bot/world/bitarray.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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
|
||||||
|
valsPerElement 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.valsPerElement) * len(b.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bitArray) Set(idx, val uint) {
|
||||||
|
var (
|
||||||
|
arrayIdx = idx / b.valsPerElement
|
||||||
|
startBit = (idx % b.valsPerElement) * 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.valsPerElement
|
||||||
|
offset = (idx % b.valsPerElement) * b.width
|
||||||
|
mask = uint64((1<<b.width - 1) << offset) // set for just the target
|
||||||
|
)
|
||||||
|
return uint(b.data[arrayIdx]&mask) >> offset
|
||||||
|
}
|
||||||
|
|
||||||
|
func valsPerBitArrayElement(bitsPerValue uint) uint {
|
||||||
|
return uint(64 / bitsPerValue)
|
||||||
|
}
|
50
bot/world/bitarray_test.go
Normal file
50
bot/world/bitarray_test.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package world
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBitArrayBasic(t *testing.T) {
|
||||||
|
a := bitArray{
|
||||||
|
width: 5,
|
||||||
|
valsPerElement: valsPerBitArrayElement(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,
|
||||||
|
valsPerElement: valsPerBitArrayElement(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,10 +3,14 @@ package world
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Tnze/go-mc/data"
|
"math"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/data/block"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const maxPaletteBits = 8
|
||||||
|
|
||||||
// DecodeChunkColumn decode the chunk data structure.
|
// DecodeChunkColumn decode the chunk data structure.
|
||||||
// If decoding went error, successful decoded data will be returned.
|
// If decoding went error, successful decoded data will be returned.
|
||||||
func DecodeChunkColumn(mask int32, data []byte) (*Chunk, error) {
|
func DecodeChunkColumn(mask int32, data []byte) (*Chunk, error) {
|
||||||
@ -26,35 +30,35 @@ func DecodeChunkColumn(mask int32, data []byte) (*Chunk, error) {
|
|||||||
return &c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func perBits(BitsPerBlock byte) int {
|
func perBits(bpb byte) uint {
|
||||||
switch {
|
switch {
|
||||||
case BitsPerBlock <= 4:
|
case bpb <= 4:
|
||||||
return 4
|
return 4
|
||||||
case BitsPerBlock < 9:
|
case bpb <= maxPaletteBits:
|
||||||
return int(BitsPerBlock)
|
return uint(bpb)
|
||||||
default:
|
default:
|
||||||
return data.BitsPerBlock // DefaultBitsPerBlock
|
return uint(block.BitsPerBlock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func readSection(data pk.DecodeReader) (s Section, err error) {
|
func readSection(data pk.DecodeReader) (s Section, err error) {
|
||||||
var BlockCount pk.Short
|
var nonAirBlockCount pk.Short
|
||||||
if err := BlockCount.Decode(data); err != nil {
|
if err := nonAirBlockCount.Decode(data); err != nil {
|
||||||
return nil, fmt.Errorf("read block count error: %w", err)
|
return nil, fmt.Errorf("block count: %w", err)
|
||||||
}
|
}
|
||||||
var bpb pk.UnsignedByte
|
var bpb pk.UnsignedByte
|
||||||
if err := bpb.Decode(data); err != nil {
|
if err := bpb.Decode(data); err != nil {
|
||||||
return nil, fmt.Errorf("read bits per block error: %w", err)
|
return nil, fmt.Errorf("bits per block: %w", err)
|
||||||
}
|
}
|
||||||
// If bpb values greater than or equal to 9, use directSection.
|
// If bpb values greater than or equal to 9, use directSection.
|
||||||
// Otherwise use paletteSection.
|
// Otherwise use paletteSection.
|
||||||
var palettes []BlockStatus
|
var palettes []BlockStatus
|
||||||
var palettesIndex map[BlockStatus]int
|
var palettesIndex map[BlockStatus]int
|
||||||
if bpb < 9 {
|
if bpb <= maxPaletteBits {
|
||||||
// read palettes
|
// read palettes
|
||||||
var length pk.VarInt
|
var length pk.VarInt
|
||||||
if err := length.Decode(data); err != nil {
|
if err := length.Decode(data); err != nil {
|
||||||
return nil, fmt.Errorf("read palettes length error: %w", err)
|
return nil, fmt.Errorf("palette length: %w", err)
|
||||||
}
|
}
|
||||||
palettes = make([]BlockStatus, length)
|
palettes = make([]BlockStatus, length)
|
||||||
palettesIndex = make(map[BlockStatus]int, length)
|
palettesIndex = make(map[BlockStatus]int, length)
|
||||||
@ -85,8 +89,15 @@ func readSection(data pk.DecodeReader) (s Section, err error) {
|
|||||||
dataArray[i] = uint64(v)
|
dataArray[i] = uint64(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
sec := directSection{bpb: perBits(byte(bpb)), data: dataArray}
|
width := perBits(byte(bpb))
|
||||||
if bpb < 9 {
|
sec := directSection{
|
||||||
|
bitArray{
|
||||||
|
width: width,
|
||||||
|
valsPerElement: valsPerBitArrayElement(width),
|
||||||
|
data: dataArray,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if bpb <= maxPaletteBits {
|
||||||
return &paletteSection{
|
return &paletteSection{
|
||||||
palette: palettes,
|
palette: palettes,
|
||||||
palettesIndex: palettesIndex,
|
palettesIndex: palettesIndex,
|
||||||
@ -98,46 +109,38 @@ func readSection(data pk.DecodeReader) (s Section, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type directSection struct {
|
type directSection struct {
|
||||||
bpb int
|
bitArray
|
||||||
data []uint64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *directSection) GetBlock(offset int) BlockStatus {
|
func (d *directSection) GetBlock(offset uint) BlockStatus {
|
||||||
offset *= d.bpb
|
return BlockStatus(d.Get(offset))
|
||||||
padding := offset % 64
|
|
||||||
block := uint32(d.data[offset/64] >> padding)
|
|
||||||
if padding > 64-d.bpb {
|
|
||||||
l := 64 - padding
|
|
||||||
block |= uint32(d.data[offset/64+1] << l)
|
|
||||||
}
|
|
||||||
return BlockStatus(block & (1<<d.bpb - 1)) // mask
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *directSection) SetBlock(offset int, s BlockStatus) {
|
func (d *directSection) SetBlock(offset uint, s BlockStatus) {
|
||||||
offset *= d.bpb
|
d.Set(offset, uint(s))
|
||||||
padding := offset % 64
|
|
||||||
mask := ^uint64((1<<d.bpb - 1) << padding)
|
|
||||||
d.data[offset/64] = d.data[offset/64]&mask | uint64(s)<<padding
|
|
||||||
if padding > 64-d.bpb {
|
|
||||||
l := padding - (64 - d.bpb)
|
|
||||||
const maxUint64 = 1<<64 - 1
|
|
||||||
d.data[offset/64+1] = d.data[offset/64+1]&(maxUint64<<l) | uint64(s)>>(64-padding)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *directSection) CanContain(s BlockStatus) bool {
|
func (d *directSection) CanContain(s BlockStatus) bool {
|
||||||
return s <= (1<<d.bpb - 1)
|
return s <= (1<<d.width - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *directSection) clone(bpb int) *directSection {
|
func (d *directSection) clone(bpb uint) *directSection {
|
||||||
newSection := &directSection{
|
out := newSectionWithSize(bpb)
|
||||||
bpb: bpb,
|
for offset := uint(0); offset < 16*16*16; offset++ {
|
||||||
data: make([]uint64, 16*16*16*bpb/64),
|
out.SetBlock(offset, d.GetBlock(offset))
|
||||||
}
|
}
|
||||||
for offset := 0; offset < 16*16*16; offset++ {
|
return out
|
||||||
newSection.SetBlock(offset, d.GetBlock(offset))
|
}
|
||||||
|
|
||||||
|
func newSectionWithSize(bpb uint) *directSection {
|
||||||
|
valsPerElement := valsPerBitArrayElement(bpb)
|
||||||
|
return &directSection{
|
||||||
|
bitArray{
|
||||||
|
width: bpb,
|
||||||
|
valsPerElement: valsPerElement,
|
||||||
|
data: make([]uint64, int(math.Ceil(16*16*16/float64(valsPerElement)))),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return newSection
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type paletteSection struct {
|
type paletteSection struct {
|
||||||
@ -146,12 +149,12 @@ type paletteSection struct {
|
|||||||
directSection
|
directSection
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *paletteSection) GetBlock(offset int) BlockStatus {
|
func (p *paletteSection) GetBlock(offset uint) BlockStatus {
|
||||||
v := p.directSection.GetBlock(offset)
|
v := p.directSection.GetBlock(offset)
|
||||||
return p.palette[v]
|
return p.palette[v]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *paletteSection) SetBlock(offset int, s BlockStatus) {
|
func (p *paletteSection) SetBlock(offset uint, s BlockStatus) {
|
||||||
if i, ok := p.palettesIndex[s]; ok {
|
if i, ok := p.palettesIndex[s]; ok {
|
||||||
p.directSection.SetBlock(offset, BlockStatus(i))
|
p.directSection.SetBlock(offset, BlockStatus(i))
|
||||||
return
|
return
|
||||||
@ -163,7 +166,7 @@ func (p *paletteSection) SetBlock(offset int, s BlockStatus) {
|
|||||||
// Increase the underlying directSection
|
// Increase the underlying directSection
|
||||||
// Suppose that old bpb fit len(p.palette) before it appended.
|
// Suppose that old bpb fit len(p.palette) before it appended.
|
||||||
// So bpb+1 must enough for new len(p.palette).
|
// So bpb+1 must enough for new len(p.palette).
|
||||||
p.directSection = *p.directSection.clone(p.bpb + 1)
|
p.directSection = *p.directSection.clone(p.width + 1)
|
||||||
}
|
}
|
||||||
p.directSection.SetBlock(offset, BlockStatus(i))
|
p.directSection.SetBlock(offset, BlockStatus(i))
|
||||||
}
|
}
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
package world
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Tnze/go-mc/data"
|
|
||||||
"math/rand"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newDirectSection(bpb int) Section {
|
|
||||||
return &directSection{
|
|
||||||
bpb: bpb,
|
|
||||||
data: make([]uint64, 16*16*16*bpb/64),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDirectSection(t *testing.T) {
|
|
||||||
for bpb := 4; bpb <= data.BitsPerBlock; bpb++ {
|
|
||||||
testSection(newDirectSection(bpb), bpb)(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDirectSection_clone(t *testing.T) {
|
|
||||||
s := newDirectSection(9)
|
|
||||||
dataset := randData(9)
|
|
||||||
for i := 0; i < 16*16*16; i++ {
|
|
||||||
s.SetBlock(i, dataset[i])
|
|
||||||
}
|
|
||||||
s = s.(*directSection).clone(data.BitsPerBlock)
|
|
||||||
for i := 0; i < 16*16*16; i++ {
|
|
||||||
if s := s.GetBlock(i); dataset[i] != s {
|
|
||||||
t.Fatalf("direct section error: want: %v, get %v", dataset[i], s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPaletteSection(t *testing.T) {
|
|
||||||
t.Run("Correctness", testSection(&paletteSection{
|
|
||||||
palettesIndex: make(map[BlockStatus]int),
|
|
||||||
directSection: *(newDirectSection(7).(*directSection)),
|
|
||||||
}, 7))
|
|
||||||
t.Run("AutomaticExpansion", testSection(&paletteSection{
|
|
||||||
palettesIndex: make(map[BlockStatus]int),
|
|
||||||
directSection: *(newDirectSection(4).(*directSection)),
|
|
||||||
}, 9))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSection(s Section, bpb int) func(t *testing.T) {
|
|
||||||
return func(t *testing.T) {
|
|
||||||
for _, dataset := range [][16 * 16 * 16]BlockStatus{secData(bpb), randData(bpb)} {
|
|
||||||
for i := 0; i < 16*16*16; i++ {
|
|
||||||
s.SetBlock(i, dataset[i])
|
|
||||||
}
|
|
||||||
for i := 0; i < 16*16*16; i++ {
|
|
||||||
if v := s.GetBlock(i); dataset[i] != v {
|
|
||||||
t.Fatalf("direct section error: want: %v, get %v", dataset[i], v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func secData(bpb int) (data [16 * 16 * 16]BlockStatus) {
|
|
||||||
mask := 1<<bpb - 1
|
|
||||||
var v int
|
|
||||||
for i := range data {
|
|
||||||
data[i] = BlockStatus(v)
|
|
||||||
v = (v + 1) & mask
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func randData(bpb int) (data [16 * 16 * 16]BlockStatus) {
|
|
||||||
data = secData(bpb)
|
|
||||||
rand.Shuffle(len(data), func(i, j int) {
|
|
||||||
data[i], data[j] = data[j], data[i]
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,20 +1,53 @@
|
|||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Tnze/go-mc/data"
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/data/entity"
|
||||||
|
item "github.com/Tnze/go-mc/data/item"
|
||||||
"github.com/Tnze/go-mc/nbt"
|
"github.com/Tnze/go-mc/nbt"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Entity is the entity of minecraft
|
// 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 {
|
type Entity struct {
|
||||||
EntityID int //实体ID
|
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
|
// The Slot data structure is how Minecraft represents an item and its associated data in the Minecraft Protocol
|
||||||
type Slot struct {
|
type Slot struct {
|
||||||
Present bool
|
Present bool
|
||||||
ItemID int32
|
ItemID item.ID
|
||||||
Count int8
|
Count int8
|
||||||
NBT interface{}
|
NBT interface{}
|
||||||
}
|
}
|
||||||
@ -25,9 +58,11 @@ func (s *Slot) Decode(r pk.DecodeReader) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if s.Present {
|
if s.Present {
|
||||||
if err := (*pk.VarInt)(&s.ItemID).Decode(r); err != nil {
|
var itemID pk.VarInt
|
||||||
|
if err := itemID.Decode(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
s.ItemID = item.ID(itemID)
|
||||||
if err := (*pk.Byte)(&s.Count).Decode(r); err != nil {
|
if err := (*pk.Byte)(&s.Count).Decode(r); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -38,6 +73,25 @@ func (s *Slot) Decode(r pk.DecodeReader) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Slot) String() string {
|
func (s Slot) Encode() []byte {
|
||||||
return data.ItemNameByID[s.ItemID]
|
if !s.Present {
|
||||||
|
return pk.Boolean(false).Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
b.Write(pk.Boolean(true).Encode())
|
||||||
|
b.Write(pk.VarInt(s.ItemID).Encode())
|
||||||
|
b.Write(pk.Byte(s.Count).Encode())
|
||||||
|
|
||||||
|
if s.NBT != nil {
|
||||||
|
nbt.NewEncoder(&b).Encode(s.NBT)
|
||||||
|
} else {
|
||||||
|
b.Write([]byte{nbt.TagEnd})
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Slot) String() string {
|
||||||
|
return item.ByID[item.ID(s.ItemID)].DisplayName
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,28 @@ package player
|
|||||||
|
|
||||||
import "github.com/Tnze/go-mc/bot/world/entity"
|
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.
|
// Player includes the player's status.
|
||||||
type Player struct {
|
type Player struct {
|
||||||
entity.Entity
|
entity.Entity
|
||||||
UUID [2]int64 //128bit UUID
|
UUID [2]int64 //128bit UUID
|
||||||
|
|
||||||
X, Y, Z float64
|
Pos Pos
|
||||||
Yaw, Pitch float32
|
|
||||||
OnGround bool
|
|
||||||
|
|
||||||
HeldItem int //拿着的物品栏位
|
HeldItem int //拿着的物品栏位
|
||||||
|
|
||||||
|
21
bot/world/tile.go
Normal file
21
bot/world/tile.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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,77 +1,15 @@
|
|||||||
package world
|
package world
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/bot/world/entity"
|
"github.com/Tnze/go-mc/bot/world/entity"
|
||||||
)
|
)
|
||||||
|
|
||||||
// World record all of the things in the world where player at
|
// World record all of the things in the world where player at
|
||||||
type World struct {
|
type World struct {
|
||||||
Entities map[int32]entity.Entity
|
entityLock sync.RWMutex
|
||||||
|
Entities map[int32]*entity.Entity
|
||||||
|
chunkLock sync.RWMutex
|
||||||
Chunks map[ChunkLoc]*Chunk
|
Chunks map[ChunkLoc]*Chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chunk store a 256*16*16 column blocks
|
|
||||||
type Chunk struct {
|
|
||||||
Sections [16]Section
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section store a 16*16*16 cube blocks
|
|
||||||
type Section interface {
|
|
||||||
// GetBlock return block status, offset can be calculate by SectionOffset.
|
|
||||||
GetBlock(offset int) BlockStatus
|
|
||||||
// SetBlock is the reverse operation of GetBlock.
|
|
||||||
SetBlock(offset int, s BlockStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SectionOffset(x, y, z int) (offset int) {
|
|
||||||
// According to wiki.vg: Data Array is given for each block with increasing x coordinates,
|
|
||||||
// within rows of increasing z coordinates, within layers of increasing y coordinates.
|
|
||||||
// So offset equals to ( x*16^0 + z*16^1 + y*16^2 )*(bits per block).
|
|
||||||
return x + z*16 + y*16*16
|
|
||||||
}
|
|
||||||
|
|
||||||
type BlockStatus uint32
|
|
||||||
|
|
||||||
type ChunkLoc struct {
|
|
||||||
X, Z int
|
|
||||||
}
|
|
||||||
|
|
||||||
// //Entity 表示一个实体
|
|
||||||
// type Entity interface {
|
|
||||||
// EntityID() int32
|
|
||||||
// }
|
|
||||||
|
|
||||||
// //Face is a face of a block
|
|
||||||
// type Face byte
|
|
||||||
|
|
||||||
// // All six faces in a block
|
|
||||||
// const (
|
|
||||||
// Bottom Face = iota
|
|
||||||
// Top
|
|
||||||
// North
|
|
||||||
// South
|
|
||||||
// West
|
|
||||||
// East
|
|
||||||
// )
|
|
||||||
|
|
||||||
// getBlock return the block in the position (x, y, z)
|
|
||||||
func (w *World) GetBlockStatus(x, y, z int) BlockStatus {
|
|
||||||
// Use n>>4 rather then n/16. It acts wrong if n<0.
|
|
||||||
c := w.Chunks[ChunkLoc{x >> 4, z >> 4}]
|
|
||||||
if c != nil {
|
|
||||||
// (n&(16-1)) == (n<0 ? n%16+16 : n%16)
|
|
||||||
if sec := c.Sections[y>>4]; sec != nil {
|
|
||||||
return sec.GetBlock(SectionOffset(x&15, y&15, z&15))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// func (b Block) String() string {
|
|
||||||
// return blockNameByID[b.id]
|
|
||||||
// }
|
|
||||||
|
|
||||||
//LoadChunk load chunk at (x, z)
|
|
||||||
func (w *World) LoadChunk(x, z int, c *Chunk) {
|
|
||||||
w.Chunks[ChunkLoc{X: x, Z: z}] = c
|
|
||||||
}
|
|
||||||
|
152
bot/world/world_chunk.go
Normal file
152
bot/world/world_chunk.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
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
|
||||||
|
}
|
167
bot/world/world_entity.go
Normal file
167
bot/world/world_entity.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
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 recieved.
|
||||||
|
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 recieved.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Printf("SpawnLivingEntity %q\n", base.Name)
|
||||||
|
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 recieved.
|
||||||
|
func (w *World) OnSpawnPlayer(pkt ptypes.SpawnPlayer) error {
|
||||||
|
w.entityLock.Lock()
|
||||||
|
defer w.entityLock.Unlock()
|
||||||
|
|
||||||
|
// fmt.Printf("SpawnPlayer %v\n", pkt)
|
||||||
|
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 recieved.
|
||||||
|
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 recieved.
|
||||||
|
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 recieved.
|
||||||
|
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 recieved.
|
||||||
|
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,10 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/bot"
|
"github.com/Tnze/go-mc/bot"
|
||||||
"github.com/Tnze/go-mc/chat"
|
"github.com/Tnze/go-mc/chat"
|
||||||
_ "github.com/Tnze/go-mc/data/lang/en-us"
|
_ "github.com/Tnze/go-mc/data/lang/en-us"
|
||||||
|
@ -2,9 +2,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/google/uuid"
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/bot"
|
"github.com/Tnze/go-mc/bot"
|
||||||
"github.com/Tnze/go-mc/chat"
|
"github.com/Tnze/go-mc/chat"
|
||||||
_ "github.com/Tnze/go-mc/data/lang/en-us"
|
_ "github.com/Tnze/go-mc/data/lang/en-us"
|
||||||
|
@ -3,8 +3,9 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Tnze/go-mc/yggdrasil"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/yggdrasil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var user = flag.String("user", "", "Can be an email address or player name for unmigrated accounts")
|
var user = flag.String("user", "", "Can be an email address or player name for unmigrated accounts")
|
||||||
|
@ -2,12 +2,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/bot"
|
"github.com/Tnze/go-mc/bot"
|
||||||
"github.com/Tnze/go-mc/data"
|
"github.com/Tnze/go-mc/data"
|
||||||
"github.com/Tnze/go-mc/net"
|
"github.com/Tnze/go-mc/net"
|
||||||
pk "github.com/Tnze/go-mc/net/packet"
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const ProtocolVersion = 578
|
const ProtocolVersion = 578
|
||||||
@ -64,7 +65,7 @@ func handlePlaying(conn net.Conn, protocol int32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
joinGame(conn)
|
joinGame(conn)
|
||||||
conn.WritePacket(pk.Marshal(data.PlayerPositionAndLookClientbound,
|
conn.WritePacket(pk.Marshal(data.PositionClientbound,
|
||||||
// https://wiki.vg/Protocol#Player_Position_And_Look_.28clientbound.29
|
// https://wiki.vg/Protocol#Player_Position_And_Look_.28clientbound.29
|
||||||
pk.Double(0), pk.Double(0), pk.Double(0), // XYZ
|
pk.Double(0), pk.Double(0), pk.Double(0), // XYZ
|
||||||
pk.Float(0), pk.Float(0), // Yaw Pitch
|
pk.Float(0), pk.Float(0), // Yaw Pitch
|
||||||
@ -139,7 +140,7 @@ func loginSuccess(conn net.Conn, name string, uuid uuid.UUID) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func joinGame(conn net.Conn) error {
|
func joinGame(conn net.Conn) error {
|
||||||
return conn.WritePacket(pk.Marshal(data.JoinGame,
|
return conn.WritePacket(pk.Marshal(data.Login,
|
||||||
pk.Int(0), // EntityID
|
pk.Int(0), // EntityID
|
||||||
pk.UnsignedByte(1), // Gamemode
|
pk.UnsignedByte(1), // Gamemode
|
||||||
pk.Int(0), // Dimension
|
pk.Int(0), // Dimension
|
||||||
|
18681
data/block/block.go
Normal file
18681
data/block/block.go
Normal file
File diff suppressed because it is too large
Load Diff
199
data/block/gen_blocks.go
Normal file
199
data/block/gen_blocks.go
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
// gen_blocks.go generates block information.
|
||||||
|
|
||||||
|
//+build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/format"
|
||||||
|
"go/token"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/iancoleman/strcase"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
infoURL = "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/pc/1.16.2/blocks.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Block struct {
|
||||||
|
ID uint32 `json:"id"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
Hardness float64 `json:"hardness"`
|
||||||
|
Diggable bool `json:"diggable"`
|
||||||
|
DropIDs []uint32 `json:"drops"`
|
||||||
|
NeedsTools map[uint32]bool `json:"harvestTools"`
|
||||||
|
|
||||||
|
MinStateID uint32 `json:"minStateId"`
|
||||||
|
MaxStateID uint32 `json:"maxStateId"`
|
||||||
|
|
||||||
|
Transparent bool `json:"transparent"`
|
||||||
|
FilterLightLevel int `json:"filterLight"`
|
||||||
|
EmitLightLevel int `json:"emitLight"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadInfo() ([]Block, error) {
|
||||||
|
resp, err := http.Get(infoURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data []Block
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeBlockDeclaration(blocks []Block) *ast.DeclStmt {
|
||||||
|
out := &ast.DeclStmt{Decl: &ast.GenDecl{Tok: token.VAR}}
|
||||||
|
|
||||||
|
for _, b := range blocks {
|
||||||
|
t := reflect.TypeOf(b)
|
||||||
|
fields := make([]ast.Expr, t.NumField())
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
ft := t.Field(i)
|
||||||
|
|
||||||
|
var val ast.Expr
|
||||||
|
switch ft.Type.Kind() {
|
||||||
|
case reflect.Uint32, reflect.Int:
|
||||||
|
val = &ast.BasicLit{Kind: token.INT, Value: fmt.Sprint(reflect.ValueOf(b).Field(i))}
|
||||||
|
case reflect.Float64:
|
||||||
|
val = &ast.BasicLit{Kind: token.FLOAT, Value: fmt.Sprint(reflect.ValueOf(b).Field(i))}
|
||||||
|
case reflect.String:
|
||||||
|
val = &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(reflect.ValueOf(b).Field(i).String())}
|
||||||
|
case reflect.Bool:
|
||||||
|
val = &ast.BasicLit{Kind: token.IDENT, Value: fmt.Sprint(reflect.ValueOf(b).Field(i).Bool())}
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
val = &ast.CompositeLit{
|
||||||
|
Type: &ast.ArrayType{
|
||||||
|
Elt: &ast.BasicLit{Kind: token.IDENT, Value: ft.Type.Elem().Name()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(b).Field(i)
|
||||||
|
switch ft.Type.Elem().Kind() {
|
||||||
|
case reflect.Uint32, reflect.Int:
|
||||||
|
for x := 0; x < v.Len(); x++ {
|
||||||
|
val.(*ast.CompositeLit).Elts = append(val.(*ast.CompositeLit).Elts, &ast.BasicLit{
|
||||||
|
Kind: token.INT,
|
||||||
|
Value: fmt.Sprint(v.Index(x)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// Must be the NeedsTools map of type map[uint32]bool.
|
||||||
|
m := &ast.CompositeLit{
|
||||||
|
Type: &ast.MapType{
|
||||||
|
Key: &ast.BasicLit{Kind: token.IDENT, Value: ft.Type.Key().Name()},
|
||||||
|
Value: &ast.BasicLit{Kind: token.IDENT, Value: ft.Type.Elem().Name()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
iter := reflect.ValueOf(b).Field(i).MapRange()
|
||||||
|
for iter.Next() {
|
||||||
|
m.Elts = append(m.Elts, &ast.KeyValueExpr{
|
||||||
|
Key: &ast.BasicLit{Kind: token.INT, Value: fmt.Sprint(iter.Key().Uint())},
|
||||||
|
Value: &ast.BasicLit{Kind: token.IDENT, Value: fmt.Sprint(iter.Value().Bool())},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
val = m
|
||||||
|
}
|
||||||
|
|
||||||
|
fields[i] = &ast.KeyValueExpr{
|
||||||
|
Key: &ast.Ident{Name: ft.Name},
|
||||||
|
Value: val,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Decl.(*ast.GenDecl).Specs = append(out.Decl.(*ast.GenDecl).Specs, &ast.ValueSpec{
|
||||||
|
Names: []*ast.Ident{{Name: strcase.ToCamel(b.Name)}},
|
||||||
|
Values: []ast.Expr{
|
||||||
|
&ast.CompositeLit{
|
||||||
|
Type: &ast.Ident{Name: reflect.TypeOf(b).Name()},
|
||||||
|
Elts: fields,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
blocks, err := downloadInfo()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(`// Package block stores information about blocks in Minecraft.
|
||||||
|
package block
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BitsPerBlock indicates how many bits are needed to represent all possible
|
||||||
|
// block states. This value is used to determine the size of the global palette.
|
||||||
|
var BitsPerBlock = int(math.Ceil(math.Log2(float64(len(StateID)))))
|
||||||
|
|
||||||
|
// ID describes the numeric ID of a block.
|
||||||
|
type ID uint32
|
||||||
|
|
||||||
|
// Block describes information about a type of block.
|
||||||
|
type Block struct {
|
||||||
|
ID ID
|
||||||
|
DisplayName string
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Hardness float64
|
||||||
|
Diggable bool
|
||||||
|
DropIDs []uint32
|
||||||
|
NeedsTools map[uint32]bool
|
||||||
|
|
||||||
|
MinStateID uint32
|
||||||
|
MaxStateID uint32
|
||||||
|
|
||||||
|
Transparent bool
|
||||||
|
FilterLightLevel int
|
||||||
|
EmitLightLevel int
|
||||||
|
}
|
||||||
|
|
||||||
|
`)
|
||||||
|
format.Node(os.Stdout, token.NewFileSet(), makeBlockDeclaration(blocks))
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("// ByID is an index of minecraft blocks by their ID.")
|
||||||
|
fmt.Println("var ByID = map[ID]*Block{")
|
||||||
|
for _, b := range blocks {
|
||||||
|
fmt.Printf(" %d: &%s,\n", b.ID, strcase.ToCamel(b.Name))
|
||||||
|
}
|
||||||
|
fmt.Println("}")
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("// StateID maps all possible state IDs to a corresponding block ID.")
|
||||||
|
fmt.Println("var StateID = map[uint32]ID{")
|
||||||
|
for _, b := range blocks {
|
||||||
|
if b.MinStateID == b.MaxStateID {
|
||||||
|
fmt.Printf(" %d: %d,\n", b.MinStateID, b.ID)
|
||||||
|
} else {
|
||||||
|
for i := b.MinStateID; i <= b.MaxStateID; i++ {
|
||||||
|
fmt.Printf(" %d: %d,\n", i, b.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("}")
|
||||||
|
}
|
132
data/block/shape/gen_shape.go
Normal file
132
data/block/shape/gen_shape.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// gen_shape.go generates block shape information.
|
||||||
|
|
||||||
|
//+build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/iancoleman/strcase"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
infoURL = "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/pc/1.16.1/blockCollisionShapes.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func downloadInfo() (map[string]interface{}, error) {
|
||||||
|
resp, err := http.Get(infoURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data map[string]interface{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
info, err := downloadInfo()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(`// Package shape stores information about the shapes of blocks in Minecraft.
|
||||||
|
package shape
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Tnze/go-mc/bot/world"
|
||||||
|
"github.com/Tnze/go-mc/data/block"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ID describes a numeric shape ID.
|
||||||
|
type ID uint32
|
||||||
|
|
||||||
|
// Shape describes how collisions should be tested for an object.
|
||||||
|
type Shape struct {
|
||||||
|
ID ID
|
||||||
|
Boxes []BoundingBox
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoundingTriplet struct {
|
||||||
|
X, Y, Z float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoundingBox struct {
|
||||||
|
Min,Max BoundingTriplet
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollisionBoxes returns the set of bounding boxes for that block state ID.
|
||||||
|
func CollisionBoxes(bStateID world.BlockStatus) ([]BoundingBox, error) {
|
||||||
|
bID := block.StateID[uint32(bStateID)]
|
||||||
|
if bID == 0 {
|
||||||
|
return nil, fmt.Errorf("unknown state ID: %v", bStateID)
|
||||||
|
}
|
||||||
|
b, ok := block.ByID[bID]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unknown block ID: %v", bID)
|
||||||
|
}
|
||||||
|
shapes, ok := ByBlockID[bID]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("unknown shape for block ID: %v", bID)
|
||||||
|
}
|
||||||
|
shapeIdx := (uint32(bStateID) - b.MinStateID) % uint32(len(shapes))
|
||||||
|
if int(shapeIdx) > len(shapes) {
|
||||||
|
return nil, fmt.Errorf("shape index out of bounds: %v >= %v", shapeIdx, len(shapes))
|
||||||
|
}
|
||||||
|
return Dimensions[shapes[shapeIdx]].Boxes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("// ByBlockID is an index of shapes for each minecraft block variant.")
|
||||||
|
fmt.Println("var ByBlockID = map[block.ID][]ID{")
|
||||||
|
blocks := info["blocks"].(map[string]interface{})
|
||||||
|
for name, shapes := range blocks {
|
||||||
|
switch s := shapes.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
set := make([]string, len(s))
|
||||||
|
for i := range s {
|
||||||
|
set[i] = fmt.Sprint(s[i])
|
||||||
|
}
|
||||||
|
fmt.Printf(" block.%s.ID: []ID{%s},\n", strcase.ToCamel(name), strings.Join(set, ", "))
|
||||||
|
default:
|
||||||
|
fmt.Printf(" block.%s.ID: []ID{%s},\n", strcase.ToCamel(name), fmt.Sprint(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("}")
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("// Dimensions describes the bounding boxes of a shape ID.")
|
||||||
|
fmt.Println("var Dimensions = map[ID]Shape{")
|
||||||
|
shapes := info["shapes"].(map[string]interface{})
|
||||||
|
for id, boxes := range shapes {
|
||||||
|
fmt.Printf(" %s: Shape{\n", id)
|
||||||
|
fmt.Printf(" ID: %s,\n", id)
|
||||||
|
fmt.Printf(" Boxes: []BoundingBox{\n")
|
||||||
|
for _, box := range boxes.([]interface{}) {
|
||||||
|
elements := box.([]interface{})
|
||||||
|
if len(elements) != 6 {
|
||||||
|
panic("expected 6 elements")
|
||||||
|
}
|
||||||
|
fmt.Printf(" {\n")
|
||||||
|
fmt.Printf(" Min: BoundingTriplet{X: %v, Y: %v, Z: %v},\n", elements[0], elements[1], elements[2])
|
||||||
|
fmt.Printf(" Max: BoundingTriplet{X: %v, Y: %v, Z: %v},\n", elements[3], elements[4], elements[5])
|
||||||
|
fmt.Printf(" },\n")
|
||||||
|
}
|
||||||
|
fmt.Printf(" },\n")
|
||||||
|
fmt.Println(" },")
|
||||||
|
}
|
||||||
|
fmt.Println("}")
|
||||||
|
}
|
4696
data/block/shape/shape.go
Normal file
4696
data/block/shape/shape.go
Normal file
File diff suppressed because it is too large
Load Diff
110053
data/blocks.go
110053
data/blocks.go
File diff suppressed because it is too large
Load Diff
257
data/entity/entity.go
Normal file
257
data/entity/entity.go
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
// Package entity stores information about entities in Minecraft.
|
||||||
|
package entity
|
||||||
|
|
||||||
|
// ID describes the numeric ID of an entity.
|
||||||
|
type ID uint32
|
||||||
|
|
||||||
|
// Category groups like entities.
|
||||||
|
type Category uint8
|
||||||
|
|
||||||
|
// Valid entity categories.
|
||||||
|
const (
|
||||||
|
Unknown Category = iota
|
||||||
|
Blocks
|
||||||
|
Immobile
|
||||||
|
Vehicles
|
||||||
|
Drops
|
||||||
|
Projectiles
|
||||||
|
PassiveMob
|
||||||
|
HostileMob
|
||||||
|
)
|
||||||
|
|
||||||
|
// Entity describes information about a type of entity.
|
||||||
|
type Entity struct {
|
||||||
|
ID ID
|
||||||
|
InternalID uint32
|
||||||
|
DisplayName string
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Width float64
|
||||||
|
Height float64
|
||||||
|
|
||||||
|
Type string
|
||||||
|
Category Category
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
AreaEffectCloud = Entity{ID: 0, InternalID: 0, DisplayName: "Area Effect Cloud", Name: "area_effect_cloud", Width: 6, Height: 0.5, Type: "mob", Category: Immobile}
|
||||||
|
ArmorStand = Entity{ID: 1, InternalID: 1, DisplayName: "Armor Stand", Name: "armor_stand", Width: 0.5, Height: 1.975, Type: "mob", Category: Immobile}
|
||||||
|
Arrow = Entity{ID: 2, InternalID: 2, DisplayName: "Arrow", Name: "arrow", Width: 0.5, Height: 0.5, Type: "mob", Category: Projectiles}
|
||||||
|
Bat = Entity{ID: 3, InternalID: 3, DisplayName: "Bat", Name: "bat", Width: 0.5, Height: 0.9, Type: "mob", Category: PassiveMob}
|
||||||
|
Bee = Entity{ID: 4, InternalID: 4, DisplayName: "Bee", Name: "bee", Width: 0.7, Height: 0.6, Type: "UNKNOWN", Category: Unknown}
|
||||||
|
Blaze = Entity{ID: 5, InternalID: 5, DisplayName: "Blaze", Name: "blaze", Width: 0.6, Height: 1.8, Type: "mob", Category: HostileMob}
|
||||||
|
Boat = Entity{ID: 6, InternalID: 6, DisplayName: "Boat", Name: "boat", Width: 1.375, Height: 0.5625, Type: "mob", Category: Vehicles}
|
||||||
|
Cat = Entity{ID: 7, InternalID: 7, DisplayName: "Cat", Name: "cat", Width: 0.6, Height: 0.7, Type: "mob", Category: PassiveMob}
|
||||||
|
CaveSpider = Entity{ID: 8, InternalID: 8, DisplayName: "Cave Spider", Name: "cave_spider", Width: 0.7, Height: 0.5, Type: "mob", Category: HostileMob}
|
||||||
|
Chicken = Entity{ID: 9, InternalID: 9, DisplayName: "Chicken", Name: "chicken", Width: 0.4, Height: 0.7, Type: "mob", Category: PassiveMob}
|
||||||
|
Cod = Entity{ID: 10, InternalID: 10, DisplayName: "Cod", Name: "cod", Width: 0.5, Height: 0.3, Type: "mob", Category: PassiveMob}
|
||||||
|
Cow = Entity{ID: 11, InternalID: 11, DisplayName: "Cow", Name: "cow", Width: 0.9, Height: 1.4, Type: "mob", Category: PassiveMob}
|
||||||
|
Creeper = Entity{ID: 12, InternalID: 12, DisplayName: "Creeper", Name: "creeper", Width: 0.6, Height: 1.7, Type: "mob", Category: HostileMob}
|
||||||
|
Dolphin = Entity{ID: 13, InternalID: 13, DisplayName: "Dolphin", Name: "dolphin", Width: 0.9, Height: 0.6, Type: "mob", Category: PassiveMob}
|
||||||
|
Donkey = Entity{ID: 14, InternalID: 14, DisplayName: "Donkey", Name: "donkey", Width: 1.39648, Height: 1.5, Type: "mob", Category: PassiveMob}
|
||||||
|
DragonFireball = Entity{ID: 15, InternalID: 15, DisplayName: "Dragon Fireball", Name: "dragon_fireball", Width: 1, Height: 1, Type: "mob", Category: Projectiles}
|
||||||
|
Drowned = Entity{ID: 16, InternalID: 16, DisplayName: "Drowned", Name: "drowned", Width: 0.6, Height: 1.95, Type: "mob", Category: HostileMob}
|
||||||
|
ElderGuardian = Entity{ID: 17, InternalID: 17, DisplayName: "Elder Guardian", Name: "elder_guardian", Width: 1.9975, Height: 1.9975, Type: "mob", Category: HostileMob}
|
||||||
|
EndCrystal = Entity{ID: 18, InternalID: 18, DisplayName: "End Crystal", Name: "end_crystal", Width: 2, Height: 2, Type: "mob", Category: Unknown}
|
||||||
|
EnderDragon = Entity{ID: 19, InternalID: 19, DisplayName: "Ender Dragon", Name: "ender_dragon", Width: 16, Height: 8, Type: "mob", Category: HostileMob}
|
||||||
|
Enderman = Entity{ID: 20, InternalID: 20, DisplayName: "Enderman", Name: "enderman", Width: 0.6, Height: 2.9, Type: "mob", Category: HostileMob}
|
||||||
|
Endermite = Entity{ID: 21, InternalID: 21, DisplayName: "Endermite", Name: "endermite", Width: 0.4, Height: 0.3, Type: "mob", Category: HostileMob}
|
||||||
|
Evoker = Entity{ID: 22, InternalID: 22, DisplayName: "Evoker", Name: "evoker", Width: 0.6, Height: 1.95, Type: "mob", Category: HostileMob}
|
||||||
|
EvokerFangs = Entity{ID: 23, InternalID: 23, DisplayName: "Evoker Fangs", Name: "evoker_fangs", Width: 0.5, Height: 0.8, Type: "mob", Category: HostileMob}
|
||||||
|
ExperienceOrb = Entity{ID: 24, InternalID: 24, DisplayName: "Experience Orb", Name: "experience_orb", Width: 0.5, Height: 0.5, Type: "mob", Category: Unknown}
|
||||||
|
EyeOfEnder = Entity{ID: 25, InternalID: 25, DisplayName: "Eye of Ender", Name: "eye_of_ender", Width: 0.25, Height: 0.25, Type: "mob", Category: Unknown}
|
||||||
|
FallingBlock = Entity{ID: 26, InternalID: 26, DisplayName: "Falling Block", Name: "falling_block", Width: 0.98, Height: 0.98, Type: "mob", Category: Blocks}
|
||||||
|
FireworkRocket = Entity{ID: 27, InternalID: 27, DisplayName: "Firework Rocket", Name: "firework_rocket", Width: 0.25, Height: 0.25, Type: "mob", Category: Unknown}
|
||||||
|
Fox = Entity{ID: 28, InternalID: 28, DisplayName: "Fox", Name: "fox", Width: 0.6, Height: 0.7, Type: "mob", Category: Unknown}
|
||||||
|
Ghast = Entity{ID: 29, InternalID: 29, DisplayName: "Ghast", Name: "ghast", Width: 4, Height: 4, Type: "mob", Category: HostileMob}
|
||||||
|
Giant = Entity{ID: 30, InternalID: 30, DisplayName: "Giant", Name: "giant", Width: 3.6, Height: 12, Type: "mob", Category: HostileMob}
|
||||||
|
Guardian = Entity{ID: 31, InternalID: 31, DisplayName: "Guardian", Name: "guardian", Width: 0.85, Height: 0.85, Type: "mob", Category: HostileMob}
|
||||||
|
Hoglin = Entity{ID: 32, InternalID: 32, DisplayName: "Hoglin", Name: "hoglin", Width: 1.39648, Height: 1.4, Type: "UNKNOWN", Category: Unknown}
|
||||||
|
Horse = Entity{ID: 33, InternalID: 33, DisplayName: "Horse", Name: "horse", Width: 1.39648, Height: 1.6, Type: "mob", Category: PassiveMob}
|
||||||
|
Husk = Entity{ID: 34, InternalID: 34, DisplayName: "Husk", Name: "husk", Width: 0.6, Height: 1.95, Type: "mob", Category: HostileMob}
|
||||||
|
Illusioner = Entity{ID: 35, InternalID: 35, DisplayName: "Illusioner", Name: "illusioner", Width: 0.6, Height: 1.95, Type: "mob", Category: HostileMob}
|
||||||
|
IronGolem = Entity{ID: 36, InternalID: 36, DisplayName: "Iron Golem", Name: "iron_golem", Width: 1.4, Height: 2.7, Type: "mob", Category: PassiveMob}
|
||||||
|
Item = Entity{ID: 37, InternalID: 37, DisplayName: "Item", Name: "item", Width: 0.25, Height: 0.25, Type: "mob", Category: Drops}
|
||||||
|
ItemFrame = Entity{ID: 38, InternalID: 38, DisplayName: "Item Frame", Name: "item_frame", Width: 0.5, Height: 0.5, Type: "mob", Category: Immobile}
|
||||||
|
Fireball = Entity{ID: 39, InternalID: 39, DisplayName: "Fireball", Name: "fireball", Width: 1, Height: 1, Type: "mob", Category: Projectiles}
|
||||||
|
LeashKnot = Entity{ID: 40, InternalID: 40, DisplayName: "Leash Knot", Name: "leash_knot", Width: 0.5, Height: 0.5, Type: "mob", Category: Immobile}
|
||||||
|
LightningBolt = Entity{ID: 41, InternalID: 41, DisplayName: "Lightning Bolt", Name: "lightning_bolt", Width: 0, Height: 0, Type: "mob", Category: Unknown}
|
||||||
|
Llama = Entity{ID: 42, InternalID: 42, DisplayName: "Llama", Name: "llama", Width: 0.9, Height: 1.87, Type: "mob", Category: PassiveMob}
|
||||||
|
LlamaSpit = Entity{ID: 43, InternalID: 43, DisplayName: "Llama Spit", Name: "llama_spit", Width: 0.25, Height: 0.25, Type: "mob", Category: Projectiles}
|
||||||
|
MagmaCube = Entity{ID: 44, InternalID: 44, DisplayName: "Magma Cube", Name: "magma_cube", Width: 2.04, Height: 2.04, Type: "mob", Category: HostileMob}
|
||||||
|
Minecart = Entity{ID: 45, InternalID: 45, DisplayName: "Minecart", Name: "minecart", Width: 0.98, Height: 0.7, Type: "mob", Category: Vehicles}
|
||||||
|
ChestMinecart = Entity{ID: 46, InternalID: 46, DisplayName: "Minecart with Chest", Name: "chest_minecart", Width: 0.98, Height: 0.7, Type: "mob", Category: Vehicles}
|
||||||
|
CommandBlockMinecart = Entity{ID: 47, InternalID: 47, DisplayName: "Minecart with Command Block", Name: "command_block_minecart", Width: 0.98, Height: 0.7, Type: "mob", Category: Vehicles}
|
||||||
|
FurnaceMinecart = Entity{ID: 48, InternalID: 48, DisplayName: "Minecart with Furnace", Name: "furnace_minecart", Width: 0.98, Height: 0.7, Type: "mob", Category: Vehicles}
|
||||||
|
HopperMinecart = Entity{ID: 49, InternalID: 49, DisplayName: "Minecart with Hopper", Name: "hopper_minecart", Width: 0.98, Height: 0.7, Type: "mob", Category: Vehicles}
|
||||||
|
SpawnerMinecart = Entity{ID: 50, InternalID: 50, DisplayName: "Minecart with Spawner", Name: "spawner_minecart", Width: 0.98, Height: 0.7, Type: "mob", Category: Vehicles}
|
||||||
|
TntMinecart = Entity{ID: 51, InternalID: 51, DisplayName: "Minecart with TNT", Name: "tnt_minecart", Width: 0.98, Height: 0.7, Type: "mob", Category: Vehicles}
|
||||||
|
Mule = Entity{ID: 52, InternalID: 52, DisplayName: "Mule", Name: "mule", Width: 1.39648, Height: 1.6, Type: "mob", Category: PassiveMob}
|
||||||
|
Mooshroom = Entity{ID: 53, InternalID: 53, DisplayName: "Mooshroom", Name: "mooshroom", Width: 0.9, Height: 1.4, Type: "mob", Category: PassiveMob}
|
||||||
|
Ocelot = Entity{ID: 54, InternalID: 54, DisplayName: "Ocelot", Name: "ocelot", Width: 0.6, Height: 0.7, Type: "mob", Category: PassiveMob}
|
||||||
|
Painting = Entity{ID: 55, InternalID: 55, DisplayName: "Painting", Name: "painting", Width: 0.5, Height: 0.5, Type: "mob", Category: Immobile}
|
||||||
|
Panda = Entity{ID: 56, InternalID: 56, DisplayName: "Panda", Name: "panda", Width: 1.3, Height: 1.25, Type: "mob", Category: PassiveMob}
|
||||||
|
Parrot = Entity{ID: 57, InternalID: 57, DisplayName: "Parrot", Name: "parrot", Width: 0.5, Height: 0.9, Type: "mob", Category: PassiveMob}
|
||||||
|
Phantom = Entity{ID: 58, InternalID: 58, DisplayName: "Phantom", Name: "phantom", Width: 0.9, Height: 0.5, Type: "mob", Category: HostileMob}
|
||||||
|
Pig = Entity{ID: 59, InternalID: 59, DisplayName: "Pig", Name: "pig", Width: 0.9, Height: 0.9, Type: "mob", Category: PassiveMob}
|
||||||
|
Piglin = Entity{ID: 60, InternalID: 60, DisplayName: "Piglin", Name: "piglin", Width: 0.6, Height: 1.95, Type: "UNKNOWN", Category: Unknown}
|
||||||
|
PiglinBrute = Entity{ID: 61, InternalID: 61, DisplayName: "Piglin Brute", Name: "piglin_brute", Width: 0.6, Height: 1.95, Type: "UNKNOWN", Category: Unknown}
|
||||||
|
Pillager = Entity{ID: 62, InternalID: 62, DisplayName: "Pillager", Name: "pillager", Width: 0.6, Height: 1.95, Type: "mob", Category: HostileMob}
|
||||||
|
PolarBear = Entity{ID: 63, InternalID: 63, DisplayName: "Polar Bear", Name: "polar_bear", Width: 1.4, Height: 1.4, Type: "mob", Category: PassiveMob}
|
||||||
|
Tnt = Entity{ID: 64, InternalID: 64, DisplayName: "Primed TNT", Name: "tnt", Width: 0.98, Height: 0.98, Type: "mob", Category: Blocks}
|
||||||
|
Pufferfish = Entity{ID: 65, InternalID: 65, DisplayName: "Pufferfish", Name: "pufferfish", Width: 0.7, Height: 0.7, Type: "mob", Category: PassiveMob}
|
||||||
|
Rabbit = Entity{ID: 66, InternalID: 66, DisplayName: "Rabbit", Name: "rabbit", Width: 0.4, Height: 0.5, Type: "mob", Category: PassiveMob}
|
||||||
|
Ravager = Entity{ID: 67, InternalID: 67, DisplayName: "Ravager", Name: "ravager", Width: 1.95, Height: 2.2, Type: "mob", Category: HostileMob}
|
||||||
|
Salmon = Entity{ID: 68, InternalID: 68, DisplayName: "Salmon", Name: "salmon", Width: 0.7, Height: 0.4, Type: "mob", Category: PassiveMob}
|
||||||
|
Sheep = Entity{ID: 69, InternalID: 69, DisplayName: "Sheep", Name: "sheep", Width: 0.9, Height: 1.3, Type: "mob", Category: PassiveMob}
|
||||||
|
Shulker = Entity{ID: 70, InternalID: 70, DisplayName: "Shulker", Name: "shulker", Width: 1, Height: 1, Type: "mob", Category: HostileMob}
|
||||||
|
ShulkerBullet = Entity{ID: 71, InternalID: 71, DisplayName: "Shulker Bullet", Name: "shulker_bullet", Width: 0.3125, Height: 0.3125, Type: "mob", Category: Projectiles}
|
||||||
|
Silverfish = Entity{ID: 72, InternalID: 72, DisplayName: "Silverfish", Name: "silverfish", Width: 0.4, Height: 0.3, Type: "mob", Category: HostileMob}
|
||||||
|
Skeleton = Entity{ID: 73, InternalID: 73, DisplayName: "Skeleton", Name: "skeleton", Width: 0.6, Height: 1.99, Type: "mob", Category: HostileMob}
|
||||||
|
SkeletonHorse = Entity{ID: 74, InternalID: 74, DisplayName: "Skeleton Horse", Name: "skeleton_horse", Width: 1.39648, Height: 1.6, Type: "mob", Category: PassiveMob}
|
||||||
|
Slime = Entity{ID: 75, InternalID: 75, DisplayName: "Slime", Name: "slime", Width: 2.04, Height: 2.04, Type: "mob", Category: HostileMob}
|
||||||
|
SmallFireball = Entity{ID: 76, InternalID: 76, DisplayName: "Small Fireball", Name: "small_fireball", Width: 0.3125, Height: 0.3125, Type: "mob", Category: Projectiles}
|
||||||
|
SnowGolem = Entity{ID: 77, InternalID: 77, DisplayName: "Snow Golem", Name: "snow_golem", Width: 0.7, Height: 1.9, Type: "mob", Category: PassiveMob}
|
||||||
|
Snowball = Entity{ID: 78, InternalID: 78, DisplayName: "Snowball", Name: "snowball", Width: 0.25, Height: 0.25, Type: "mob", Category: Projectiles}
|
||||||
|
SpectralArrow = Entity{ID: 79, InternalID: 79, DisplayName: "Spectral Arrow", Name: "spectral_arrow", Width: 0.5, Height: 0.5, Type: "mob", Category: Projectiles}
|
||||||
|
Spider = Entity{ID: 80, InternalID: 80, DisplayName: "Spider", Name: "spider", Width: 1.4, Height: 0.9, Type: "mob", Category: HostileMob}
|
||||||
|
Squid = Entity{ID: 81, InternalID: 81, DisplayName: "Squid", Name: "squid", Width: 0.8, Height: 0.8, Type: "mob", Category: PassiveMob}
|
||||||
|
Stray = Entity{ID: 82, InternalID: 82, DisplayName: "Stray", Name: "stray", Width: 0.6, Height: 1.99, Type: "mob", Category: HostileMob}
|
||||||
|
Strider = Entity{ID: 83, InternalID: 83, DisplayName: "Strider", Name: "strider", Width: 0.9, Height: 1.7, Type: "UNKNOWN", Category: Unknown}
|
||||||
|
Egg = Entity{ID: 84, InternalID: 84, DisplayName: "Thrown Egg", Name: "egg", Width: 0.25, Height: 0.25, Type: "mob", Category: Projectiles}
|
||||||
|
EnderPearl = Entity{ID: 85, InternalID: 85, DisplayName: "Thrown Ender Pearl", Name: "ender_pearl", Width: 0.25, Height: 0.25, Type: "mob", Category: Projectiles}
|
||||||
|
ExperienceBottle = Entity{ID: 86, InternalID: 86, DisplayName: "Thrown Bottle o' Enchanting", Name: "experience_bottle", Width: 0.25, Height: 0.25, Type: "mob", Category: Unknown}
|
||||||
|
Potion = Entity{ID: 87, InternalID: 87, DisplayName: "Potion", Name: "potion", Width: 0.25, Height: 0.25, Type: "mob", Category: Projectiles}
|
||||||
|
Trident = Entity{ID: 88, InternalID: 88, DisplayName: "Trident", Name: "trident", Width: 0.5, Height: 0.5, Type: "mob", Category: Unknown}
|
||||||
|
TraderLlama = Entity{ID: 89, InternalID: 89, DisplayName: "Trader Llama", Name: "trader_llama", Width: 0.9, Height: 1.87, Type: "mob", Category: PassiveMob}
|
||||||
|
TropicalFish = Entity{ID: 90, InternalID: 90, DisplayName: "Tropical Fish", Name: "tropical_fish", Width: 0.5, Height: 0.4, Type: "mob", Category: PassiveMob}
|
||||||
|
Turtle = Entity{ID: 91, InternalID: 91, DisplayName: "Turtle", Name: "turtle", Width: 1.2, Height: 0.4, Type: "mob", Category: PassiveMob}
|
||||||
|
Vex = Entity{ID: 92, InternalID: 92, DisplayName: "Vex", Name: "vex", Width: 0.4, Height: 0.8, Type: "mob", Category: HostileMob}
|
||||||
|
Villager = Entity{ID: 93, InternalID: 93, DisplayName: "Villager", Name: "villager", Width: 0.6, Height: 1.95, Type: "mob", Category: PassiveMob}
|
||||||
|
Vindicator = Entity{ID: 94, InternalID: 94, DisplayName: "Vindicator", Name: "vindicator", Width: 0.6, Height: 1.95, Type: "mob", Category: HostileMob}
|
||||||
|
WanderingTrader = Entity{ID: 95, InternalID: 95, DisplayName: "Wandering Trader", Name: "wandering_trader", Width: 0.6, Height: 1.95, Type: "mob", Category: PassiveMob}
|
||||||
|
Witch = Entity{ID: 96, InternalID: 96, DisplayName: "Witch", Name: "witch", Width: 0.6, Height: 1.95, Type: "mob", Category: HostileMob}
|
||||||
|
Wither = Entity{ID: 97, InternalID: 97, DisplayName: "Wither", Name: "wither", Width: 0.9, Height: 3.5, Type: "mob", Category: HostileMob}
|
||||||
|
WitherSkeleton = Entity{ID: 98, InternalID: 98, DisplayName: "Wither Skeleton", Name: "wither_skeleton", Width: 0.7, Height: 2.4, Type: "mob", Category: HostileMob}
|
||||||
|
WitherSkull = Entity{ID: 99, InternalID: 99, DisplayName: "Wither Skull", Name: "wither_skull", Width: 0.3125, Height: 0.3125, Type: "mob", Category: Projectiles}
|
||||||
|
Wolf = Entity{ID: 100, InternalID: 100, DisplayName: "Wolf", Name: "wolf", Width: 0.6, Height: 0.85, Type: "mob", Category: PassiveMob}
|
||||||
|
Zoglin = Entity{ID: 101, InternalID: 101, DisplayName: "Zoglin", Name: "zoglin", Width: 1.39648, Height: 1.4, Type: "UNKNOWN", Category: Unknown}
|
||||||
|
Zombie = Entity{ID: 102, InternalID: 102, DisplayName: "Zombie", Name: "zombie", Width: 0.6, Height: 1.95, Type: "mob", Category: HostileMob}
|
||||||
|
ZombieHorse = Entity{ID: 103, InternalID: 103, DisplayName: "Zombie Horse", Name: "zombie_horse", Width: 1.39648, Height: 1.6, Type: "mob", Category: PassiveMob}
|
||||||
|
ZombieVillager = Entity{ID: 104, InternalID: 104, DisplayName: "Zombie Villager", Name: "zombie_villager", Width: 0.6, Height: 1.95, Type: "mob", Category: HostileMob}
|
||||||
|
ZombifiedPiglin = Entity{ID: 105, InternalID: 105, DisplayName: "Zombified Piglin", Name: "zombified_piglin", Width: 0.6, Height: 1.95, Type: "UNKNOWN", Category: Unknown}
|
||||||
|
Player = Entity{ID: 106, InternalID: 106, DisplayName: "Player", Name: "player", Width: 0.6, Height: 1.8, Type: "mob", Category: Unknown}
|
||||||
|
FishingBobber = Entity{ID: 107, InternalID: 107, DisplayName: "Fishing Bobber", Name: "fishing_bobber", Width: 0.25, Height: 0.25, Type: "mob", Category: Unknown}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ByID is an index of minecraft entities by their ID.
|
||||||
|
var ByID = map[ID]*Entity{
|
||||||
|
0: &AreaEffectCloud,
|
||||||
|
1: &ArmorStand,
|
||||||
|
2: &Arrow,
|
||||||
|
3: &Bat,
|
||||||
|
4: &Bee,
|
||||||
|
5: &Blaze,
|
||||||
|
6: &Boat,
|
||||||
|
7: &Cat,
|
||||||
|
8: &CaveSpider,
|
||||||
|
9: &Chicken,
|
||||||
|
10: &Cod,
|
||||||
|
11: &Cow,
|
||||||
|
12: &Creeper,
|
||||||
|
13: &Dolphin,
|
||||||
|
14: &Donkey,
|
||||||
|
15: &DragonFireball,
|
||||||
|
16: &Drowned,
|
||||||
|
17: &ElderGuardian,
|
||||||
|
18: &EndCrystal,
|
||||||
|
19: &EnderDragon,
|
||||||
|
20: &Enderman,
|
||||||
|
21: &Endermite,
|
||||||
|
22: &Evoker,
|
||||||
|
23: &EvokerFangs,
|
||||||
|
24: &ExperienceOrb,
|
||||||
|
25: &EyeOfEnder,
|
||||||
|
26: &FallingBlock,
|
||||||
|
27: &FireworkRocket,
|
||||||
|
28: &Fox,
|
||||||
|
29: &Ghast,
|
||||||
|
30: &Giant,
|
||||||
|
31: &Guardian,
|
||||||
|
32: &Hoglin,
|
||||||
|
33: &Horse,
|
||||||
|
34: &Husk,
|
||||||
|
35: &Illusioner,
|
||||||
|
36: &IronGolem,
|
||||||
|
37: &Item,
|
||||||
|
38: &ItemFrame,
|
||||||
|
39: &Fireball,
|
||||||
|
40: &LeashKnot,
|
||||||
|
41: &LightningBolt,
|
||||||
|
42: &Llama,
|
||||||
|
43: &LlamaSpit,
|
||||||
|
44: &MagmaCube,
|
||||||
|
45: &Minecart,
|
||||||
|
46: &ChestMinecart,
|
||||||
|
47: &CommandBlockMinecart,
|
||||||
|
48: &FurnaceMinecart,
|
||||||
|
49: &HopperMinecart,
|
||||||
|
50: &SpawnerMinecart,
|
||||||
|
51: &TntMinecart,
|
||||||
|
52: &Mule,
|
||||||
|
53: &Mooshroom,
|
||||||
|
54: &Ocelot,
|
||||||
|
55: &Painting,
|
||||||
|
56: &Panda,
|
||||||
|
57: &Parrot,
|
||||||
|
58: &Phantom,
|
||||||
|
59: &Pig,
|
||||||
|
60: &Piglin,
|
||||||
|
61: &PiglinBrute,
|
||||||
|
62: &Pillager,
|
||||||
|
63: &PolarBear,
|
||||||
|
64: &Tnt,
|
||||||
|
65: &Pufferfish,
|
||||||
|
66: &Rabbit,
|
||||||
|
67: &Ravager,
|
||||||
|
68: &Salmon,
|
||||||
|
69: &Sheep,
|
||||||
|
70: &Shulker,
|
||||||
|
71: &ShulkerBullet,
|
||||||
|
72: &Silverfish,
|
||||||
|
73: &Skeleton,
|
||||||
|
74: &SkeletonHorse,
|
||||||
|
75: &Slime,
|
||||||
|
76: &SmallFireball,
|
||||||
|
77: &SnowGolem,
|
||||||
|
78: &Snowball,
|
||||||
|
79: &SpectralArrow,
|
||||||
|
80: &Spider,
|
||||||
|
81: &Squid,
|
||||||
|
82: &Stray,
|
||||||
|
83: &Strider,
|
||||||
|
84: &Egg,
|
||||||
|
85: &EnderPearl,
|
||||||
|
86: &ExperienceBottle,
|
||||||
|
87: &Potion,
|
||||||
|
88: &Trident,
|
||||||
|
89: &TraderLlama,
|
||||||
|
90: &TropicalFish,
|
||||||
|
91: &Turtle,
|
||||||
|
92: &Vex,
|
||||||
|
93: &Villager,
|
||||||
|
94: &Vindicator,
|
||||||
|
95: &WanderingTrader,
|
||||||
|
96: &Witch,
|
||||||
|
97: &Wither,
|
||||||
|
98: &WitherSkeleton,
|
||||||
|
99: &WitherSkull,
|
||||||
|
100: &Wolf,
|
||||||
|
101: &Zoglin,
|
||||||
|
102: &Zombie,
|
||||||
|
103: &ZombieHorse,
|
||||||
|
104: &ZombieVillager,
|
||||||
|
105: &ZombifiedPiglin,
|
||||||
|
106: &Player,
|
||||||
|
107: &FishingBobber,
|
||||||
|
}
|
184
data/entity/gen_entity.go
Normal file
184
data/entity/gen_entity.go
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
// gen_entity.go generates entity information.
|
||||||
|
|
||||||
|
//+build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/format"
|
||||||
|
"go/token"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/iancoleman/strcase"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
infoURL = "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/pc/1.16.2/entities.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Entity struct {
|
||||||
|
ID uint32 `json:"id"`
|
||||||
|
InternalID uint32 `json:"internalId"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
Width float64 `json:"width"`
|
||||||
|
Height float64 `json:"height"`
|
||||||
|
|
||||||
|
Type string `json:"type"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadInfo() ([]Entity, error) {
|
||||||
|
resp, err := http.Get(infoURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data []Entity
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeEntityDeclaration(entities []Entity) *ast.DeclStmt {
|
||||||
|
out := &ast.DeclStmt{Decl: &ast.GenDecl{Tok: token.VAR}}
|
||||||
|
|
||||||
|
for _, e := range entities {
|
||||||
|
t := reflect.TypeOf(e)
|
||||||
|
fields := make([]ast.Expr, t.NumField())
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
ft := t.Field(i)
|
||||||
|
|
||||||
|
if ft.Name == "Category" {
|
||||||
|
val := &ast.BasicLit{Kind: token.IDENT, Value: "Unknown"}
|
||||||
|
switch e.Category {
|
||||||
|
case "Passive mobs":
|
||||||
|
val.Value = "PassiveMob"
|
||||||
|
case "Hostile mobs":
|
||||||
|
val.Value = "HostileMob"
|
||||||
|
case "UNKNOWN":
|
||||||
|
default:
|
||||||
|
val.Value = e.Category
|
||||||
|
}
|
||||||
|
fields[i] = &ast.KeyValueExpr{
|
||||||
|
Key: &ast.Ident{Name: ft.Name},
|
||||||
|
Value: val,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var val ast.Expr
|
||||||
|
switch ft.Type.Kind() {
|
||||||
|
case reflect.Uint32, reflect.Int:
|
||||||
|
val = &ast.BasicLit{Kind: token.INT, Value: fmt.Sprint(reflect.ValueOf(e).Field(i))}
|
||||||
|
case reflect.Float64:
|
||||||
|
val = &ast.BasicLit{Kind: token.FLOAT, Value: fmt.Sprint(reflect.ValueOf(e).Field(i))}
|
||||||
|
case reflect.String:
|
||||||
|
val = &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(reflect.ValueOf(e).Field(i).String())}
|
||||||
|
case reflect.Bool:
|
||||||
|
val = &ast.BasicLit{Kind: token.IDENT, Value: fmt.Sprint(reflect.ValueOf(e).Field(i).Bool())}
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
val = &ast.CompositeLit{
|
||||||
|
Type: &ast.ArrayType{
|
||||||
|
Elt: &ast.BasicLit{Kind: token.IDENT, Value: ft.Type.Elem().Name()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(e).Field(i)
|
||||||
|
switch ft.Type.Elem().Kind() {
|
||||||
|
case reflect.Uint32, reflect.Int:
|
||||||
|
for x := 0; x < v.Len(); x++ {
|
||||||
|
val.(*ast.CompositeLit).Elts = append(val.(*ast.CompositeLit).Elts, &ast.BasicLit{
|
||||||
|
Kind: token.INT,
|
||||||
|
Value: fmt.Sprint(v.Index(x)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fields[i] = &ast.KeyValueExpr{
|
||||||
|
Key: &ast.Ident{Name: ft.Name},
|
||||||
|
Value: val,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Decl.(*ast.GenDecl).Specs = append(out.Decl.(*ast.GenDecl).Specs, &ast.ValueSpec{
|
||||||
|
Names: []*ast.Ident{{Name: strcase.ToCamel(e.Name)}},
|
||||||
|
Values: []ast.Expr{
|
||||||
|
&ast.CompositeLit{
|
||||||
|
Type: &ast.Ident{Name: reflect.TypeOf(e).Name()},
|
||||||
|
Elts: fields,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
entities, err := downloadInfo()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(`// Package entity stores information about entities in Minecraft.
|
||||||
|
package entity
|
||||||
|
|
||||||
|
// ID describes the numeric ID of an entity.
|
||||||
|
type ID uint32
|
||||||
|
|
||||||
|
// Category groups like entities.
|
||||||
|
type Category uint8
|
||||||
|
|
||||||
|
// Valid entity categories.
|
||||||
|
const (
|
||||||
|
Unknown Category = iota
|
||||||
|
Blocks
|
||||||
|
Immobile
|
||||||
|
Vehicles
|
||||||
|
Drops
|
||||||
|
Projectiles
|
||||||
|
PassiveMob
|
||||||
|
HostileMob
|
||||||
|
)
|
||||||
|
|
||||||
|
// Entity describes information about a type of entity.
|
||||||
|
type Entity struct {
|
||||||
|
ID ID
|
||||||
|
InternalID uint32
|
||||||
|
DisplayName string
|
||||||
|
Name string
|
||||||
|
|
||||||
|
Width float64
|
||||||
|
Height float64
|
||||||
|
|
||||||
|
Type string
|
||||||
|
Category Category
|
||||||
|
}
|
||||||
|
|
||||||
|
`)
|
||||||
|
format.Node(os.Stdout, token.NewFileSet(), makeEntityDeclaration(entities))
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("// ByID is an index of minecraft entities by their ID.")
|
||||||
|
fmt.Println("var ByID = map[ID]*Entity{")
|
||||||
|
for _, e := range entities {
|
||||||
|
fmt.Printf(" %d: &%s,\n", e.ID, strcase.ToCamel(e.Name))
|
||||||
|
}
|
||||||
|
fmt.Println("}")
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
}
|
196
data/gen_packetIDs.go
Normal file
196
data/gen_packetIDs.go
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
// gen_packetIDs.go generates the enumeration of packet IDs used on the wire.
|
||||||
|
|
||||||
|
//+build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/iancoleman/strcase"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
protocolURL = "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/pc/1.16.2/protocol.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// unnest is a utility function to unpack a value from a nested map, given
|
||||||
|
// an arbitrary set of keys to reach through.
|
||||||
|
func unnest(input map[string]interface{}, keys ...string) (map[string]interface{}, error) {
|
||||||
|
for _, k := range keys {
|
||||||
|
sub, ok := input[k]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("key %q not found", k)
|
||||||
|
}
|
||||||
|
next, ok := sub.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("key %q was %T, expected a string map", k, sub)
|
||||||
|
}
|
||||||
|
input = next
|
||||||
|
}
|
||||||
|
return input, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type duplexMappings struct {
|
||||||
|
Clientbound map[string]string
|
||||||
|
Serverbound map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *duplexMappings) EnsureUniqueNames() {
|
||||||
|
// Assemble a slice of keys to check across both maps, because we cannot
|
||||||
|
// mutate a map while iterating it.
|
||||||
|
clientKeys := make([]string, 0, len(m.Clientbound))
|
||||||
|
for k, _ := range m.Clientbound {
|
||||||
|
clientKeys = append(clientKeys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range clientKeys {
|
||||||
|
if _, alsoServerKey := m.Serverbound[k]; alsoServerKey {
|
||||||
|
cVal, sVal := m.Clientbound[k], m.Serverbound[k]
|
||||||
|
delete(m.Clientbound, k)
|
||||||
|
delete(m.Serverbound, k)
|
||||||
|
m.Clientbound[k+"Clientbound"] = cVal
|
||||||
|
m.Serverbound[k+"Serverbound"] = sVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackMapping returns the set of packet IDs and their names for a given
|
||||||
|
// game state.
|
||||||
|
func unpackMapping(data map[string]interface{}, gameState string) (duplexMappings, error) {
|
||||||
|
out := duplexMappings{
|
||||||
|
Clientbound: make(map[string]string),
|
||||||
|
Serverbound: make(map[string]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := unnest(data, gameState, "toClient", "types")
|
||||||
|
if err != nil {
|
||||||
|
return duplexMappings{}, err
|
||||||
|
}
|
||||||
|
pType := info["packet"].([]interface{})[1].([]interface{})[0].(map[string]interface{})["type"]
|
||||||
|
mappings := pType.([]interface{})[1].(map[string]interface{})["mappings"].(map[string]interface{})
|
||||||
|
for k, v := range mappings {
|
||||||
|
out.Clientbound[strcase.ToCamel(v.(string))] = k
|
||||||
|
}
|
||||||
|
info, err = unnest(data, gameState, "toServer", "types")
|
||||||
|
if err != nil {
|
||||||
|
return duplexMappings{}, err
|
||||||
|
}
|
||||||
|
pType = info["packet"].([]interface{})[1].([]interface{})[0].(map[string]interface{})["type"]
|
||||||
|
mappings = pType.([]interface{})[1].(map[string]interface{})["mappings"].(map[string]interface{})
|
||||||
|
for k, v := range mappings {
|
||||||
|
out.Serverbound[strcase.ToCamel(v.(string))] = k
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type protocolIDs struct {
|
||||||
|
Login duplexMappings
|
||||||
|
Play duplexMappings
|
||||||
|
Status duplexMappings
|
||||||
|
// Handshake state has no packets
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p protocolIDs) MaxLen() int {
|
||||||
|
var max int
|
||||||
|
for _, m := range []duplexMappings{p.Login, p.Play, p.Status} {
|
||||||
|
for k, _ := range m.Clientbound {
|
||||||
|
if len(k) > max {
|
||||||
|
max = len(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, _ := range m.Serverbound {
|
||||||
|
if len(k) > max {
|
||||||
|
max = len(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadInfo() (*protocolIDs, error) {
|
||||||
|
resp, err := http.Get(protocolURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
var data map[string]interface{}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out protocolIDs
|
||||||
|
if out.Login, err = unpackMapping(data, "login"); err != nil {
|
||||||
|
return nil, fmt.Errorf("login: %v", err)
|
||||||
|
}
|
||||||
|
out.Login.EnsureUniqueNames()
|
||||||
|
if out.Play, err = unpackMapping(data, "play"); err != nil {
|
||||||
|
return nil, fmt.Errorf("play: %v", err)
|
||||||
|
}
|
||||||
|
out.Play.EnsureUniqueNames()
|
||||||
|
if out.Status, err = unpackMapping(data, "status"); err != nil {
|
||||||
|
return nil, fmt.Errorf("play: %v", err)
|
||||||
|
}
|
||||||
|
out.Status.EnsureUniqueNames()
|
||||||
|
|
||||||
|
return &out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
pIDs, err := downloadInfo()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxLen := pIDs.MaxLen()
|
||||||
|
|
||||||
|
fmt.Println("package data")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("//go:generate bash -c \"go run gen_packetIDs.go > packetIDs.go\"")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("// This file is automatically generated by gen_packetIDs.go. DO NOT EDIT.")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("// PktID represents a packet ID used in the minecraft protocol.")
|
||||||
|
fmt.Println("type PktID int32")
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("// Valid PktID values.")
|
||||||
|
fmt.Println("const (")
|
||||||
|
|
||||||
|
fmt.Println(" // Clientbound packets for connections in the login state.")
|
||||||
|
for k, v := range pIDs.Login.Clientbound {
|
||||||
|
fmt.Printf(" %s%s PktID = %s\n", k, strings.Repeat(" ", maxLen-len(k)), v)
|
||||||
|
}
|
||||||
|
fmt.Println(" // Serverbound packets for connections in the login state.")
|
||||||
|
for k, v := range pIDs.Login.Serverbound {
|
||||||
|
fmt.Printf(" %s%s PktID = %s\n", k, strings.Repeat(" ", maxLen-len(k)), v)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Println(" // Clientbound packets for connections in the play state.")
|
||||||
|
for k, v := range pIDs.Play.Clientbound {
|
||||||
|
fmt.Printf(" %s%s PktID = %s\n", k, strings.Repeat(" ", maxLen-len(k)), v)
|
||||||
|
}
|
||||||
|
fmt.Println(" // Serverbound packets for connections in the play state.")
|
||||||
|
for k, v := range pIDs.Play.Serverbound {
|
||||||
|
fmt.Printf(" %s%s PktID = %s\n", k, strings.Repeat(" ", maxLen-len(k)), v)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Println(" // Clientbound packets used to respond to ping/status requests.")
|
||||||
|
for k, v := range pIDs.Status.Clientbound {
|
||||||
|
fmt.Printf(" %s%s PktID = %s\n", k, strings.Repeat(" ", maxLen-len(k)), v)
|
||||||
|
}
|
||||||
|
fmt.Println(" // Serverbound packets used to ping or read server status.")
|
||||||
|
for k, v := range pIDs.Status.Serverbound {
|
||||||
|
fmt.Printf(" %s%s PktID = %s\n", k, strings.Repeat(" ", maxLen-len(k)), v)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Println(")")
|
||||||
|
}
|
30
data/inv/inv.go
Normal file
30
data/inv/inv.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Package inv maps window types to inventory slot information.
|
||||||
|
package inv
|
||||||
|
|
||||||
|
type Info struct {
|
||||||
|
Name string
|
||||||
|
Start, End int // Player inventory
|
||||||
|
Slots int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Info) PlayerInvStart() int {
|
||||||
|
return i.Start
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Info) PlayerInvEnd() int {
|
||||||
|
return i.End
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Info) HotbarIdx(place int) int {
|
||||||
|
return i.End - (8 - place)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ByType = map[int]Info{
|
||||||
|
-1: Info{Name: "inventory", Start: 9, End: 44, Slots: 46},
|
||||||
|
0: Info{Name: "generic_9x1", Start: 1 * 9, End: 1*9 + 35, Slots: 1*9 + 36},
|
||||||
|
1: Info{Name: "generic_9x2", Start: 2 * 9, End: 2*9 + 35, Slots: 2*9 + 36},
|
||||||
|
2: Info{Name: "generic_9x3", Start: 3 * 9, End: 3*9 + 35, Slots: 3*9 + 36},
|
||||||
|
3: Info{Name: "generic_9x4", Start: 4 * 9, End: 4*9 + 35, Slots: 4*9 + 36},
|
||||||
|
4: Info{Name: "generic_9x5", Start: 5 * 9, End: 5*9 + 35, Slots: 5*9 + 36},
|
||||||
|
5: Info{Name: "generic_9x6", Start: 6 * 9, End: 6*9 + 35, Slots: 6*9 + 36},
|
||||||
|
}
|
168
data/item/gen_item.go
Normal file
168
data/item/gen_item.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// gen_blocks.go generates block information.
|
||||||
|
|
||||||
|
//+build ignore
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/format"
|
||||||
|
"go/token"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/iancoleman/strcase"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
infoURL = "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/pc/1.16.2/items.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Item struct {
|
||||||
|
ID uint32 `json:"id"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
StackSize uint `json:"stackSize"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadInfo() ([]Item, error) {
|
||||||
|
resp, err := http.Get(infoURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
var data []Item
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeItemDeclaration(blocks []Item) *ast.DeclStmt {
|
||||||
|
out := &ast.DeclStmt{Decl: &ast.GenDecl{Tok: token.VAR}}
|
||||||
|
|
||||||
|
for _, b := range blocks {
|
||||||
|
t := reflect.TypeOf(b)
|
||||||
|
fields := make([]ast.Expr, t.NumField())
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
ft := t.Field(i)
|
||||||
|
|
||||||
|
var val ast.Expr
|
||||||
|
switch ft.Type.Kind() {
|
||||||
|
case reflect.Uint32, reflect.Int, reflect.Uint:
|
||||||
|
val = &ast.BasicLit{Kind: token.INT, Value: fmt.Sprint(reflect.ValueOf(b).Field(i))}
|
||||||
|
case reflect.Float64:
|
||||||
|
val = &ast.BasicLit{Kind: token.FLOAT, Value: fmt.Sprint(reflect.ValueOf(b).Field(i))}
|
||||||
|
case reflect.String:
|
||||||
|
val = &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(reflect.ValueOf(b).Field(i).String())}
|
||||||
|
case reflect.Bool:
|
||||||
|
val = &ast.BasicLit{Kind: token.IDENT, Value: fmt.Sprint(reflect.ValueOf(b).Field(i).Bool())}
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
val = &ast.CompositeLit{
|
||||||
|
Type: &ast.ArrayType{
|
||||||
|
Elt: &ast.BasicLit{Kind: token.IDENT, Value: ft.Type.Elem().Name()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(b).Field(i)
|
||||||
|
switch ft.Type.Elem().Kind() {
|
||||||
|
case reflect.Uint32, reflect.Int:
|
||||||
|
for x := 0; x < v.Len(); x++ {
|
||||||
|
val.(*ast.CompositeLit).Elts = append(val.(*ast.CompositeLit).Elts, &ast.BasicLit{
|
||||||
|
Kind: token.INT,
|
||||||
|
Value: fmt.Sprint(v.Index(x)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// Must be the NeedsTools map of type map[uint32]bool.
|
||||||
|
m := &ast.CompositeLit{
|
||||||
|
Type: &ast.MapType{
|
||||||
|
Key: &ast.BasicLit{Kind: token.IDENT, Value: ft.Type.Key().Name()},
|
||||||
|
Value: &ast.BasicLit{Kind: token.IDENT, Value: ft.Type.Elem().Name()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
iter := reflect.ValueOf(b).Field(i).MapRange()
|
||||||
|
for iter.Next() {
|
||||||
|
m.Elts = append(m.Elts, &ast.KeyValueExpr{
|
||||||
|
Key: &ast.BasicLit{Kind: token.INT, Value: fmt.Sprint(iter.Key().Uint())},
|
||||||
|
Value: &ast.BasicLit{Kind: token.IDENT, Value: fmt.Sprint(iter.Value().Bool())},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
val = m
|
||||||
|
}
|
||||||
|
|
||||||
|
fields[i] = &ast.KeyValueExpr{
|
||||||
|
Key: &ast.Ident{Name: ft.Name},
|
||||||
|
Value: val,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Decl.(*ast.GenDecl).Specs = append(out.Decl.(*ast.GenDecl).Specs, &ast.ValueSpec{
|
||||||
|
Names: []*ast.Ident{{Name: strcase.ToCamel(b.Name)}},
|
||||||
|
Values: []ast.Expr{
|
||||||
|
&ast.CompositeLit{
|
||||||
|
Type: &ast.Ident{Name: reflect.TypeOf(b).Name()},
|
||||||
|
Elts: fields,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
items, err := downloadInfo()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(`// Package item stores information about items in Minecraft.
|
||||||
|
package item
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ID describes the numeric ID of an item.
|
||||||
|
type ID uint32
|
||||||
|
|
||||||
|
// Item describes information about a type of item.
|
||||||
|
type Item struct {
|
||||||
|
ID ID
|
||||||
|
DisplayName string
|
||||||
|
Name string
|
||||||
|
StackSize uint
|
||||||
|
}
|
||||||
|
|
||||||
|
`)
|
||||||
|
format.Node(os.Stdout, token.NewFileSet(), makeItemDeclaration(items))
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("// ByID is an index of minecraft items by their ID.")
|
||||||
|
fmt.Println("var ByID = map[ID]*Item{")
|
||||||
|
for _, i := range items {
|
||||||
|
fmt.Printf(" %d: &%s,\n", i.ID, strcase.ToCamel(i.Name))
|
||||||
|
}
|
||||||
|
fmt.Println("}")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Println("// ByName is an index of minecraft items by their name.")
|
||||||
|
fmt.Println("var ByName = map[string]*Item{")
|
||||||
|
for _, i := range items {
|
||||||
|
fmt.Printf(" %q: &%s,\n", i.Name, strcase.ToCamel(i.Name))
|
||||||
|
}
|
||||||
|
fmt.Println("}")
|
||||||
|
fmt.Println()
|
||||||
|
}
|
2952
data/item/item.go
Normal file
2952
data/item/item.go
Normal file
File diff suppressed because it is too large
Load Diff
2064
data/items.go
2064
data/items.go
File diff suppressed because it is too large
Load Diff
@ -1,156 +1,173 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
// Clientbound packet IDs
|
//go:generate bash -c "go run gen_packetIDs.go > packetIDs.go"
|
||||||
|
|
||||||
|
// This file is automatically generated by gen_packetIDs.go. DO NOT EDIT.
|
||||||
|
|
||||||
|
// PktID represents a packet ID used in the minecraft protocol.
|
||||||
|
type PktID int32
|
||||||
|
|
||||||
|
// Valid PktID values.
|
||||||
const (
|
const (
|
||||||
SpawnObject int32 = iota //0x00
|
// Clientbound packets for connections in the login state.
|
||||||
SpawnExperienceOrb
|
EncryptionBeginClientbound PktID = 0x01
|
||||||
SpawnLivingEntity
|
Success PktID = 0x02
|
||||||
SpawnPainting
|
Compress PktID = 0x03
|
||||||
SpawnPlayer
|
LoginPluginRequest PktID = 0x04
|
||||||
EntityAnimationClientbound
|
Disconnect PktID = 0x00
|
||||||
Statistics
|
// Serverbound packets for connections in the login state.
|
||||||
AcknowledgePlayerDigging
|
EncryptionBeginServerbound PktID = 0x01
|
||||||
BlockBreakAnimation
|
LoginPluginResponse PktID = 0x02
|
||||||
BlockEntityData
|
LoginStart PktID = 0x00
|
||||||
BlockAction
|
|
||||||
BlockChange
|
|
||||||
BossBar
|
|
||||||
ServerDifficulty
|
|
||||||
ChatMessageClientbound
|
|
||||||
TabComplete
|
|
||||||
|
|
||||||
DeclareCommands //0x10
|
// Clientbound packets for connections in the play state.
|
||||||
WindowConfirmationClientbound
|
EntityMetadata PktID = 0x44
|
||||||
CloseWindowClientbound
|
Teams PktID = 0x4c
|
||||||
WindowItems
|
BossBar PktID = 0x0c
|
||||||
WindowProperty
|
Map PktID = 0x25
|
||||||
SetSlot
|
Difficulty PktID = 0x0d
|
||||||
SetCooldown
|
Camera PktID = 0x3e
|
||||||
PluginMessageClientbound
|
WindowItems PktID = 0x13
|
||||||
NamedSoundEffect
|
ScoreboardObjective PktID = 0x4a
|
||||||
DisconnectPlay
|
RelEntityMove PktID = 0x27
|
||||||
EntityStatus
|
DeclareCommands PktID = 0x10
|
||||||
Explosion
|
CombatEvent PktID = 0x31
|
||||||
UnloadChunk
|
SpawnEntityPainting PktID = 0x03
|
||||||
ChangeGameState
|
EntityMoveLook PktID = 0x28
|
||||||
OpenHorseWindow
|
ScoreboardScore PktID = 0x4d
|
||||||
KeepAliveClientbound
|
Title PktID = 0x4f
|
||||||
|
CraftProgressBar PktID = 0x14
|
||||||
|
NamedEntitySpawn PktID = 0x04
|
||||||
|
ScoreboardDisplayObjective PktID = 0x43
|
||||||
|
WorldParticles PktID = 0x22
|
||||||
|
OpenWindow PktID = 0x2d
|
||||||
|
MultiBlockChange PktID = 0x3b
|
||||||
|
EntitySoundEffect PktID = 0x50
|
||||||
|
Tags PktID = 0x5b
|
||||||
|
EntityUpdateAttributes PktID = 0x58
|
||||||
|
NamedSoundEffect PktID = 0x18
|
||||||
|
GameStateChange PktID = 0x1d
|
||||||
|
PlayerInfo PktID = 0x32
|
||||||
|
Advancements PktID = 0x57
|
||||||
|
Explosion PktID = 0x1b
|
||||||
|
KeepAliveClientbound PktID = 0x1f
|
||||||
|
MapChunk PktID = 0x20
|
||||||
|
AttachEntity PktID = 0x45
|
||||||
|
TradeList PktID = 0x26
|
||||||
|
Respawn PktID = 0x39
|
||||||
|
EntityDestroy PktID = 0x36
|
||||||
|
Experience PktID = 0x48
|
||||||
|
EntityLook PktID = 0x29
|
||||||
|
OpenBook PktID = 0x2c
|
||||||
|
WorldEvent PktID = 0x21
|
||||||
|
DeclareRecipes PktID = 0x5a
|
||||||
|
UnlockRecipes PktID = 0x35
|
||||||
|
EntityEquipment PktID = 0x47
|
||||||
|
EntityVelocity PktID = 0x46
|
||||||
|
Animation PktID = 0x05
|
||||||
|
UpdateViewDistance PktID = 0x41
|
||||||
|
HeldItemSlotClientbound PktID = 0x3f
|
||||||
|
NbtQueryResponse PktID = 0x54
|
||||||
|
Entity PktID = 0x2a
|
||||||
|
UpdateViewPosition PktID = 0x40
|
||||||
|
AbilitiesClientbound PktID = 0x30
|
||||||
|
OpenSignEntity PktID = 0x2e
|
||||||
|
SetSlot PktID = 0x15
|
||||||
|
PlayerlistHeader PktID = 0x53
|
||||||
|
ResourcePackSend PktID = 0x38
|
||||||
|
SpawnEntityExperienceOrb PktID = 0x01
|
||||||
|
Collect PktID = 0x55
|
||||||
|
Statistics PktID = 0x06
|
||||||
|
TileEntityData PktID = 0x09
|
||||||
|
ChatClientbound PktID = 0x0e
|
||||||
|
WorldBorder PktID = 0x3d
|
||||||
|
UnloadChunk PktID = 0x1c
|
||||||
|
UpdateLight PktID = 0x23
|
||||||
|
UpdateHealth PktID = 0x49
|
||||||
|
RemoveEntityEffect PktID = 0x37
|
||||||
|
KickDisconnect PktID = 0x19
|
||||||
|
CustomPayloadClientbound PktID = 0x17
|
||||||
|
SpawnPosition PktID = 0x42
|
||||||
|
EntityStatus PktID = 0x1a
|
||||||
|
SpawnEntity PktID = 0x00
|
||||||
|
SetPassengers PktID = 0x4b
|
||||||
|
FacePlayer PktID = 0x33
|
||||||
|
TransactionClientbound PktID = 0x11
|
||||||
|
BlockAction PktID = 0x0a
|
||||||
|
BlockBreakAnimation PktID = 0x08
|
||||||
|
BlockChange PktID = 0x0b
|
||||||
|
Login PktID = 0x24
|
||||||
|
VehicleMoveClientbound PktID = 0x2b
|
||||||
|
CraftRecipeResponse PktID = 0x2f
|
||||||
|
SetCooldown PktID = 0x16
|
||||||
|
StopSound PktID = 0x52
|
||||||
|
EntityEffect PktID = 0x59
|
||||||
|
CloseWindowClientbound PktID = 0x12
|
||||||
|
AcknowledgePlayerDigging PktID = 0x07
|
||||||
|
SelectAdvancementTab PktID = 0x3c
|
||||||
|
EntityHeadRotation PktID = 0x3a
|
||||||
|
TabCompleteClientbound PktID = 0x0f
|
||||||
|
PositionClientbound PktID = 0x34
|
||||||
|
OpenHorseWindow PktID = 0x1e
|
||||||
|
UpdateTime PktID = 0x4e
|
||||||
|
SpawnEntityLiving PktID = 0x02
|
||||||
|
EntityTeleport PktID = 0x56
|
||||||
|
SoundEffect PktID = 0x51
|
||||||
|
// Serverbound packets for connections in the play state.
|
||||||
|
SetBeaconEffect PktID = 0x24
|
||||||
|
UpdateStructureBlock PktID = 0x2a
|
||||||
|
EnchantItem PktID = 0x08
|
||||||
|
Spectate PktID = 0x2d
|
||||||
|
ArmAnimation PktID = 0x2c
|
||||||
|
QueryEntityNbt PktID = 0x0d
|
||||||
|
Flying PktID = 0x15
|
||||||
|
ResourcePackReceive PktID = 0x21
|
||||||
|
BlockDig PktID = 0x1b
|
||||||
|
AbilitiesServerbound PktID = 0x1a
|
||||||
|
SelectTrade PktID = 0x23
|
||||||
|
UpdateJigsawBlock PktID = 0x29
|
||||||
|
SteerVehicle PktID = 0x1d
|
||||||
|
Settings PktID = 0x05
|
||||||
|
SteerBoat PktID = 0x17
|
||||||
|
UseItem PktID = 0x2f
|
||||||
|
TeleportConfirm PktID = 0x00
|
||||||
|
PickItem PktID = 0x18
|
||||||
|
CraftRecipeRequest PktID = 0x19
|
||||||
|
Look PktID = 0x14
|
||||||
|
TabCompleteServerbound PktID = 0x06
|
||||||
|
AdvancementTab PktID = 0x22
|
||||||
|
ClientCommand PktID = 0x04
|
||||||
|
EntityAction PktID = 0x1c
|
||||||
|
PositionServerbound PktID = 0x12
|
||||||
|
TransactionServerbound PktID = 0x07
|
||||||
|
UpdateSign PktID = 0x2b
|
||||||
|
QueryBlockNbt PktID = 0x01
|
||||||
|
PositionLook PktID = 0x13
|
||||||
|
HeldItemSlotServerbound PktID = 0x25
|
||||||
|
EditBook PktID = 0x0c
|
||||||
|
UpdateCommandBlock PktID = 0x26
|
||||||
|
UseEntity PktID = 0x0e
|
||||||
|
GenerateStructure PktID = 0x0f
|
||||||
|
NameItem PktID = 0x20
|
||||||
|
RecipeBook PktID = 0x1f
|
||||||
|
KeepAliveServerbound PktID = 0x10
|
||||||
|
CustomPayloadServerbound PktID = 0x0b
|
||||||
|
VehicleMoveServerbound PktID = 0x16
|
||||||
|
CloseWindowServerbound PktID = 0x0a
|
||||||
|
UpdateCommandBlockMinecart PktID = 0x27
|
||||||
|
WindowClick PktID = 0x09
|
||||||
|
ChatServerbound PktID = 0x03
|
||||||
|
SetCreativeSlot PktID = 0x28
|
||||||
|
DisplayedRecipe PktID = 0x1e
|
||||||
|
BlockPlace PktID = 0x2e
|
||||||
|
LockDifficulty PktID = 0x11
|
||||||
|
SetDifficulty PktID = 0x02
|
||||||
|
|
||||||
ChunkData //0x20
|
// Clientbound packets used to respond to ping/status requests.
|
||||||
Effect
|
ServerInfo PktID = 0x00
|
||||||
Particle
|
PingClientbound PktID = 0x01
|
||||||
UpdateLight
|
// Serverbound packets used to ping or read server status.
|
||||||
JoinGame
|
PingStart PktID = 0x00
|
||||||
MapData
|
PingServerbound PktID = 0x01
|
||||||
TradeList
|
|
||||||
EntityRelativeMove
|
|
||||||
EntityLookAndRelativeMove
|
|
||||||
EntityLook
|
|
||||||
Entity
|
|
||||||
VehicleMoveClientbound
|
|
||||||
OpenBook
|
|
||||||
OpenWindow
|
|
||||||
OpenSignEditor
|
|
||||||
CraftRecipeResponse
|
|
||||||
|
|
||||||
PlayerAbilitiesClientbound //0x30
|
|
||||||
CombatEvent
|
|
||||||
PlayerInfo
|
|
||||||
FacePlayer
|
|
||||||
PlayerPositionAndLookClientbound
|
|
||||||
UnlockRecipes
|
|
||||||
DestroyEntities
|
|
||||||
RemoveEntityEffect
|
|
||||||
ResourcePackSend
|
|
||||||
Respawn
|
|
||||||
EntityHeadLook
|
|
||||||
MultiBlockChange
|
|
||||||
SelectAdvancementTab
|
|
||||||
WorldBorder
|
|
||||||
Camera
|
|
||||||
HeldItemChangeClientbound
|
|
||||||
|
|
||||||
UpdateViewPosition //0x40
|
|
||||||
UpdateViewDistance
|
|
||||||
SpawnPosition
|
|
||||||
DisplayScoreboard
|
|
||||||
EntityMetadata
|
|
||||||
AttachEntity
|
|
||||||
EntityVelocity
|
|
||||||
EntityEquipment
|
|
||||||
SetExperience
|
|
||||||
UpdateHealth
|
|
||||||
ScoreboardObjective
|
|
||||||
SetPassengers
|
|
||||||
Teams
|
|
||||||
UpdateScore
|
|
||||||
TimeUpdate
|
|
||||||
Title
|
|
||||||
|
|
||||||
EntitySoundEffect //0x50
|
|
||||||
SoundEffect
|
|
||||||
StopSound
|
|
||||||
PlayerListHeaderAndFooter
|
|
||||||
NBTQueryResponse
|
|
||||||
CollectItem
|
|
||||||
EntityTeleport
|
|
||||||
Advancements
|
|
||||||
EntityProperties
|
|
||||||
EntityEffect
|
|
||||||
DeclareRecipes
|
|
||||||
Tags //0x5B
|
|
||||||
)
|
|
||||||
|
|
||||||
// Serverbound packet IDs
|
|
||||||
const (
|
|
||||||
TeleportConfirm int32 = iota //0x00
|
|
||||||
QueryBlockNBT
|
|
||||||
SetDifficulty
|
|
||||||
ChatMessageServerbound
|
|
||||||
ClientStatus
|
|
||||||
ClientSettings
|
|
||||||
TabCompleteServerbound
|
|
||||||
ConfirmTransactionServerbound
|
|
||||||
ClickWindowButton
|
|
||||||
ClickWindow
|
|
||||||
CloseWindowServerbound
|
|
||||||
PluginMessageServerbound
|
|
||||||
EditBook
|
|
||||||
QueryEntityNBT
|
|
||||||
UseEntity
|
|
||||||
GenerateStructure
|
|
||||||
|
|
||||||
KeepAliveServerbound //0x10
|
|
||||||
LockDifficulty
|
|
||||||
PlayerPosition
|
|
||||||
PlayerPositionAndLookServerbound
|
|
||||||
PlayerLook
|
|
||||||
Player
|
|
||||||
VehicleMoveServerbound
|
|
||||||
SteerBoat
|
|
||||||
PickItem
|
|
||||||
CraftRecipeRequest
|
|
||||||
PlayerAbilitiesServerbound
|
|
||||||
PlayerDigging
|
|
||||||
EntityAction
|
|
||||||
SteerVehicle
|
|
||||||
DisplayedRecipe
|
|
||||||
RecipeBookData
|
|
||||||
|
|
||||||
NameItem //0x20
|
|
||||||
ResourcePackStatus
|
|
||||||
AdvancementTab
|
|
||||||
SelectTrade
|
|
||||||
SetBeaconEffect
|
|
||||||
HeldItemChangeServerbound
|
|
||||||
UpdateCommandBlock
|
|
||||||
UpdateCommandBlockMinecart
|
|
||||||
CreativeInventoryAction
|
|
||||||
UpdateJigsawBlock
|
|
||||||
UpdateStructureBlock
|
|
||||||
UpdateSign
|
|
||||||
AnimationServerbound
|
|
||||||
Spectate
|
|
||||||
PlayerBlockPlacement
|
|
||||||
UseItem //0x2F
|
|
||||||
)
|
)
|
||||||
|
6
go.mod
6
go.mod
@ -2,4 +2,8 @@ module github.com/Tnze/go-mc
|
|||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require github.com/google/uuid v1.1.1
|
require (
|
||||||
|
github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482
|
||||||
|
github.com/google/uuid v1.1.1
|
||||||
|
github.com/iancoleman/strcase v0.1.1 // indirect
|
||||||
|
)
|
||||||
|
4
go.sum
4
go.sum
@ -1,2 +1,6 @@
|
|||||||
|
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/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
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/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/iancoleman/strcase v0.1.1 h1:2I+LRClyCYB7JgZb9U0k75VHUiQe9RfknRqDyUfzp7k=
|
||||||
|
github.com/iancoleman/strcase v0.1.1/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"compress/zlib"
|
"compress/zlib"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Packet define a net data package
|
// Packet define a net data package
|
||||||
@ -14,8 +16,8 @@ type Packet struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Marshal generate Packet with the ID and Fields
|
//Marshal generate Packet with the ID and Fields
|
||||||
func Marshal(ID int32, fields ...FieldEncoder) (pk Packet) {
|
func Marshal(id data.PktID, fields ...FieldEncoder) (pk Packet) {
|
||||||
pk.ID = ID
|
pk.ID = int32(id)
|
||||||
|
|
||||||
for _, v := range fields {
|
for _, v := range fields {
|
||||||
pk.Data = append(pk.Data, v.Encode()...)
|
pk.Data = append(pk.Data, v.Encode()...)
|
||||||
|
@ -2,10 +2,11 @@ package packet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/google/uuid"
|
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/Tnze/go-mc/nbt"
|
"github.com/Tnze/go-mc/nbt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -364,6 +365,16 @@ func (p *Position) Decode(r DecodeReader) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Decodes an Angle
|
||||||
|
func (b *Angle) Decode(r DecodeReader) error {
|
||||||
|
v, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*b = Angle(v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
//Encode a Float
|
//Encode a Float
|
||||||
func (f Float) Encode() []byte {
|
func (f Float) Encode() []byte {
|
||||||
return Int(math.Float32bits(float32(f))).Encode()
|
return Int(math.Float32bits(float32(f))).Encode()
|
||||||
|
128
net/ptypes/chunk.go
Normal file
128
net/ptypes/chunk.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// Package ptypes implements encoding and decoding for high-level packets.
|
||||||
|
package ptypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"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 clientbound packet which describes a chunk.
|
||||||
|
type ChunkData struct {
|
||||||
|
X, Z pk.Int
|
||||||
|
FullChunk pk.Boolean
|
||||||
|
PrimaryBitMask pk.VarInt
|
||||||
|
Heightmaps struct{}
|
||||||
|
Biomes biomesData
|
||||||
|
Data chunkData
|
||||||
|
BlockEntities blockEntities
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChunkData) Decode(pkt pk.Packet) error {
|
||||||
|
r := bytes.NewReader(pkt.Data)
|
||||||
|
if err := p.X.Decode(r); err != nil {
|
||||||
|
return fmt.Errorf("X: %v", err)
|
||||||
|
}
|
||||||
|
if err := p.Z.Decode(r); err != nil {
|
||||||
|
return fmt.Errorf("Z: %v", err)
|
||||||
|
}
|
||||||
|
if err := p.FullChunk.Decode(r); err != nil {
|
||||||
|
return fmt.Errorf("full chunk: %v", err)
|
||||||
|
}
|
||||||
|
if err := p.PrimaryBitMask.Decode(r); err != nil {
|
||||||
|
return fmt.Errorf("bit mask: %v", err)
|
||||||
|
}
|
||||||
|
if err := (pk.NBT{V: &p.Heightmaps}).Decode(r); err != nil {
|
||||||
|
return fmt.Errorf("heightmaps: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Biome data is only present for full chunks.
|
||||||
|
if p.FullChunk {
|
||||||
|
if err := p.Biomes.Decode(r); err != nil {
|
||||||
|
return fmt.Errorf("heightmaps: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.Data.Decode(r); err != nil {
|
||||||
|
return fmt.Errorf("data: %v", err)
|
||||||
|
}
|
||||||
|
if err := p.BlockEntities.Decode(r); err != nil {
|
||||||
|
return fmt.Errorf("block entities: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type biomesData struct {
|
||||||
|
data []pk.VarInt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *biomesData) Decode(r pk.DecodeReader) error {
|
||||||
|
var nobd pk.VarInt // Number of Biome Datums
|
||||||
|
if err := nobd.Decode(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.data = make([]pk.VarInt, nobd)
|
||||||
|
|
||||||
|
for i := 0; i < int(nobd); i++ {
|
||||||
|
var d pk.VarInt
|
||||||
|
if err := d.Decode(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.data[i] = d
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type chunkData []byte
|
||||||
|
type blockEntities []entity.BlockEntity
|
||||||
|
|
||||||
|
// Decode implement net.packet.FieldDecoder
|
||||||
|
func (c *chunkData) Decode(r pk.DecodeReader) error {
|
||||||
|
var sz pk.VarInt
|
||||||
|
if err := sz.Decode(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*c = make([]byte, sz)
|
||||||
|
if _, err := r.Read(*c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode implement net.packet.FieldDecoder
|
||||||
|
func (b *blockEntities) Decode(r pk.DecodeReader) error {
|
||||||
|
var sz pk.VarInt // Number of BlockEntities
|
||||||
|
if err := sz.Decode(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*b = make(blockEntities, sz)
|
||||||
|
decoder := nbt.NewDecoder(r)
|
||||||
|
for i := 0; i < int(sz); i++ {
|
||||||
|
if err := decoder.Decode(&(*b)[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TileEntityData describes a change to a tile entity.
|
||||||
|
type TileEntityData struct {
|
||||||
|
Pos pk.Position
|
||||||
|
Action pk.UnsignedByte
|
||||||
|
Data entity.BlockEntity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TileEntityData) Decode(pkt pk.Packet) error {
|
||||||
|
r := bytes.NewReader(pkt.Data)
|
||||||
|
if err := p.Pos.Decode(r); err != nil {
|
||||||
|
return fmt.Errorf("position: %v", err)
|
||||||
|
}
|
||||||
|
if err := p.Action.Decode(r); err != nil {
|
||||||
|
return fmt.Errorf("action: %v", err)
|
||||||
|
}
|
||||||
|
return nbt.NewDecoder(r).Decode(&p.Data)
|
||||||
|
}
|
95
net/ptypes/entities.go
Normal file
95
net/ptypes/entities.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package ptypes
|
||||||
|
|
||||||
|
import pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
|
||||||
|
// SpawnEntity is a clientbound 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 clientbound 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 clientbound 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 animationf 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)
|
||||||
|
}
|
89
net/ptypes/inventory.go
Normal file
89
net/ptypes/inventory.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// Package ptypes implements encoding and decoding for high-level packets.
|
||||||
|
package ptypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/bot/world/entity"
|
||||||
|
"github.com/Tnze/go-mc/chat"
|
||||||
|
"github.com/Tnze/go-mc/data"
|
||||||
|
"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) Decode(pkt pk.Packet) error {
|
||||||
|
r := bytes.NewReader(pkt.Data)
|
||||||
|
if err := p.WindowID.Decode(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var count pk.Short
|
||||||
|
if err := count.Decode(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Slots = make([]entity.Slot, int(count))
|
||||||
|
for i := 0; i < int(count); i++ {
|
||||||
|
if err := p.Slots[i].Decode(r); err != nil && !errors.Is(err, nbt.ErrEND) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
data.TransactionServerbound,
|
||||||
|
p.WindowID,
|
||||||
|
p.ActionID,
|
||||||
|
p.Accepted,
|
||||||
|
)
|
||||||
|
}
|
91
net/ptypes/misc.go
Normal file
91
net/ptypes/misc.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package ptypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/Tnze/go-mc/chat"
|
||||||
|
"github.com/Tnze/go-mc/data"
|
||||||
|
pk "github.com/Tnze/go-mc/net/packet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SoundEffect is a clientbound 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 clientbound 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) Encode() []byte {
|
||||||
|
return []byte(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PluginData) Decode(r pk.DecodeReader) error {
|
||||||
|
data, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p = data
|
||||||
|
return 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(
|
||||||
|
data.CustomPayloadServerbound,
|
||||||
|
p.Channel,
|
||||||
|
p.Data,
|
||||||
|
)
|
||||||
|
}
|
87
net/ptypes/motion.go
Normal file
87
net/ptypes/motion.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// Package ptypes implements encoding and decoding for high-level packets.
|
||||||
|
package ptypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Tnze/go-mc/data"
|
||||||
|
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(
|
||||||
|
data.PositionLook,
|
||||||
|
pk.Double(p.X),
|
||||||
|
pk.Double(p.Y),
|
||||||
|
pk.Double(p.Z),
|
||||||
|
pk.Float(p.Yaw),
|
||||||
|
pk.Float(p.Pitch),
|
||||||
|
pk.Boolean(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(
|
||||||
|
data.PositionServerbound,
|
||||||
|
pk.Double(p.X),
|
||||||
|
pk.Double(p.Y),
|
||||||
|
pk.Double(p.Z),
|
||||||
|
pk.Boolean(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(
|
||||||
|
data.Look,
|
||||||
|
pk.Float(p.Yaw),
|
||||||
|
pk.Float(p.Pitch),
|
||||||
|
pk.Boolean(p.OnGround),
|
||||||
|
)
|
||||||
|
}
|
32
net/ptypes/world.go
Normal file
32
net/ptypes/world.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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