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

|
||||

|
||||

|
||||

|
||||
[](https://godoc.org/github.com/Tnze/go-mc)
|
||||
[](https://goreportcard.com/report/github.com/Tnze/go-mc)
|
||||
[](https://travis-ci.org/Tnze/go-mc)
|
||||
@ -105,4 +105,4 @@ Originally it's all right to write a bot with only `go-mc/net` package. But cons
|
||||
|
||||
理论上讲,只用`go-mc/net`包实现一个bot是完全可行的,但是为了节省大家从头去理解MC握手、登录、加密等协议的过程,在`go-mc/bot`中我已经把这些都实现了,只不过它不是跨版本的。你可以直接使用,或者作为自己实现的参考。
|
||||
|
||||
Now, go and have a look at the example!
|
||||
Now, go and have a look at the example!
|
||||
|
@ -1,10 +1,18 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Tnze/go-mc/bot/path"
|
||||
"github.com/Tnze/go-mc/bot/phy"
|
||||
"github.com/Tnze/go-mc/bot/world"
|
||||
"github.com/Tnze/go-mc/bot/world/entity"
|
||||
"github.com/Tnze/go-mc/bot/world/entity/player"
|
||||
"github.com/Tnze/go-mc/data"
|
||||
"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
|
||||
@ -14,14 +22,35 @@ type Client struct {
|
||||
|
||||
player.Player
|
||||
PlayInfo
|
||||
ServInfo
|
||||
abilities PlayerAbilities
|
||||
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.
|
||||
// Do not send at the same goroutine!
|
||||
Delegate chan func() error
|
||||
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.
|
||||
@ -31,34 +60,37 @@ type Client struct {
|
||||
//
|
||||
// For online-mode, you need login your Mojang account
|
||||
// and load your Name, UUID and AccessToken to client.
|
||||
func NewClient() (c *Client) {
|
||||
c = new(Client)
|
||||
|
||||
//init Client
|
||||
c.settings = DefaultSettings
|
||||
c.Name = "Steve"
|
||||
c.Delegate = make(chan func() error)
|
||||
|
||||
c.Wd = world.World{
|
||||
Entities: make(map[int32]entity.Entity),
|
||||
Chunks: make(map[world.ChunkLoc]*world.Chunk),
|
||||
func NewClient() *Client {
|
||||
return &Client{
|
||||
settings: DefaultSettings,
|
||||
Auth: Auth{Name: "Steve"},
|
||||
Delegate: make(chan func() error),
|
||||
Wd: world.World{
|
||||
Entities: make(map[int32]*entity.Entity, 8192),
|
||||
Chunks: make(map[world.ChunkLoc]*world.Chunk, 2048),
|
||||
},
|
||||
closing: make(chan struct{}),
|
||||
inbound: make(chan pk.Packet, 5),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//PlayInfo content player info in server.
|
||||
type PlayInfo struct {
|
||||
Gamemode int //游戏模式
|
||||
Hardcore bool //是否是极限模式
|
||||
Dimension int //维度
|
||||
Difficulty int //难度
|
||||
ViewDistance int //视距
|
||||
ReducedDebugInfo bool //减少调试信息
|
||||
WorldName string //当前世界的名字
|
||||
IsDebug bool //调试
|
||||
IsFlat bool //超平坦世界
|
||||
// SpawnPosition Position //主世界出生点
|
||||
Gamemode int //游戏模式
|
||||
Hardcore bool //是否是极限模式
|
||||
Dimension int //维度
|
||||
Difficulty int //难度
|
||||
ViewDistance int //视距
|
||||
ReducedDebugInfo bool //减少调试信息
|
||||
WorldName string //当前世界的名字
|
||||
IsDebug bool //调试
|
||||
IsFlat bool //超平坦世界
|
||||
SpawnPosition Position //主世界出生点
|
||||
}
|
||||
|
||||
// ServInfo contains information about the server implementation.
|
||||
type ServInfo struct {
|
||||
Brand string
|
||||
}
|
||||
|
||||
// PlayerAbilities defines what player can do.
|
||||
|
63
bot/event.go
63
bot/event.go
@ -2,13 +2,38 @@ package bot
|
||||
|
||||
import (
|
||||
"github.com/Tnze/go-mc/bot/world/entity"
|
||||
"github.com/Tnze/go-mc/bot/world/entity/player"
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
"github.com/google/uuid"
|
||||
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/Tnze/go-mc/net/ptypes"
|
||||
)
|
||||
|
||||
type seenPacketFlags uint8
|
||||
|
||||
// Valid seenPacketFlags values.
|
||||
const (
|
||||
seenJoinGame seenPacketFlags = 1 << iota
|
||||
seenServerDifficulty
|
||||
seenPlayerAbilities
|
||||
seenPlayerInventory
|
||||
seenUpdateLight
|
||||
seenChunkData
|
||||
seenPlayerPositionAndLook
|
||||
seenSpawnPos
|
||||
|
||||
// gameReadyMinPackets are the minimum set of packets that must be seen, for
|
||||
// the GameReady callback to be invoked.
|
||||
gameReadyMinPackets = seenJoinGame | seenChunkData | seenUpdateLight |
|
||||
seenPlayerAbilities | seenPlayerInventory | seenServerDifficulty |
|
||||
seenPlayerPositionAndLook | seenSpawnPos
|
||||
)
|
||||
|
||||
type eventBroker struct {
|
||||
seenPackets seenPacketFlags
|
||||
isReady bool
|
||||
|
||||
GameStart func() error
|
||||
ChatMsg func(msg chat.Message, pos byte, sender uuid.UUID) error
|
||||
Disconnect func(reason chat.Message) error
|
||||
@ -17,6 +42,7 @@ type eventBroker struct {
|
||||
SoundPlay func(name string, category int, x, y, z float64, volume, pitch float32) error
|
||||
PluginMessage func(channel string, data []byte) error
|
||||
HeldItemChange func(slot int) error
|
||||
OpenWindow func(pkt ptypes.OpenWindow) error
|
||||
|
||||
// ExperienceChange will be called every time player's experience level updates.
|
||||
// Parameters:
|
||||
@ -25,10 +51,39 @@ type eventBroker struct {
|
||||
// total - total amount of experience received from level 0.
|
||||
ExperienceChange func(bar float32, level int32, total int32) error
|
||||
|
||||
WindowsItem func(id byte, slots []entity.Slot) error
|
||||
WindowsItemChange func(id byte, slotID int, slot entity.Slot) error
|
||||
WindowsItem func(id byte, slots []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.
|
||||
// Default handler will run only if pass == false.
|
||||
// ServerDifficultyChange is called whenever the gamemode of the server changes.
|
||||
// At time of writing (1.16.3), difficulty values of 0, 1, 2, and 3 correspond
|
||||
// to peaceful, easy, normal, and hard respectively.
|
||||
ServerDifficultyChange func(difficulty int) error
|
||||
|
||||
// GameReady is called after the client has joined the server and successfully
|
||||
// received player state. Additionally, the server has begun sending world
|
||||
// state (such as lighting and chunk information).
|
||||
//
|
||||
// Use this callback as a signal as to when your bot should start 'doing'
|
||||
// things.
|
||||
GameReady func() error
|
||||
|
||||
// PositionChange is called whenever the player position is updated.
|
||||
PositionChange func(pos player.Pos) error
|
||||
|
||||
// ReceivePacket will be called when new packets arrive.
|
||||
// The default handler will run only if pass == false.
|
||||
ReceivePacket func(p pk.Packet) (pass bool, err error)
|
||||
|
||||
// PrePhysics will be called before a 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
|
||||
}
|
||||
|
928
bot/ingame.go
928
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"
|
||||
"strconv"
|
||||
|
||||
"github.com/Tnze/go-mc/data"
|
||||
mcnet "github.com/Tnze/go-mc/net"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
// ProtocolVersion , the protocol version number of minecraft net protocol
|
||||
const ProtocolVersion = 751
|
||||
const ProtocolVersion = 753
|
||||
|
||||
// JoinServer connect a Minecraft server for playing the game.
|
||||
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
|
||||
// uuid, l := pk.UnpackString(pack.Data)
|
||||
// name, _ := unpackString(pack.Data[l:])
|
||||
return //switches the connection state to PLAY.
|
||||
return nil
|
||||
case 0x03: //Set Compression
|
||||
var threshold pk.VarInt
|
||||
if err := pack.Scan(&threshold); err != nil {
|
||||
@ -124,3 +125,13 @@ type Dialer interface {
|
||||
func (c *Client) Conn() *mcnet.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"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/Tnze/go-mc/net/ptypes"
|
||||
)
|
||||
|
||||
// SwingArm swing player's arm.
|
||||
@ -13,7 +14,7 @@ import (
|
||||
// It's just animation.
|
||||
func (c *Client) SwingArm(hand int) error {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
data.AnimationServerbound,
|
||||
data.ArmAnimation,
|
||||
pk.VarInt(hand),
|
||||
))
|
||||
}
|
||||
@ -21,7 +22,7 @@ func (c *Client) SwingArm(hand int) error {
|
||||
// Respawn the player when it was dead.
|
||||
func (c *Client) Respawn() error {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
data.ClientStatus,
|
||||
data.ClientCommand,
|
||||
pk.VarInt(0),
|
||||
))
|
||||
}
|
||||
@ -77,18 +78,17 @@ func (c *Client) Chat(msg string) error {
|
||||
}
|
||||
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
data.ChatMessageServerbound,
|
||||
data.ChatServerbound,
|
||||
pk.String(msg),
|
||||
))
|
||||
}
|
||||
|
||||
// PluginMessage is used by mods and plugins to send their data.
|
||||
func (c *Client) PluginMessage(channal string, msg []byte) error {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
data.PluginMessageServerbound,
|
||||
pk.Identifier(channal),
|
||||
pluginMessageData(msg),
|
||||
))
|
||||
func (c *Client) PluginMessage(channel string, msg []byte) error {
|
||||
return c.conn.WritePacket((&ptypes.PluginMessage{
|
||||
Channel: pk.Identifier(channel),
|
||||
Data: ptypes.PluginData(msg),
|
||||
}).Encode())
|
||||
}
|
||||
|
||||
// UseBlock is used to place or use a block.
|
||||
@ -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.
|
||||
func (c *Client) UseBlock(hand, locX, locY, locZ, face int, cursorX, cursorY, cursorZ float32, insideBlock bool) error {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
data.PlayerBlockPlacement,
|
||||
data.UseItem,
|
||||
pk.VarInt(hand),
|
||||
pk.Position{X: locX, Y: locY, Z: locZ},
|
||||
pk.VarInt(face),
|
||||
@ -120,7 +120,7 @@ func (c *Client) SelectItem(slot int) error {
|
||||
}
|
||||
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
data.HeldItemChangeServerbound,
|
||||
data.HeldItemSlotServerbound,
|
||||
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 {
|
||||
return c.conn.WritePacket(pk.Marshal(
|
||||
data.PlayerDigging,
|
||||
data.BlockDig,
|
||||
pk.VarInt(status),
|
||||
pk.Position{X: locX, Y: locY, Z: locZ},
|
||||
pk.Byte(face),
|
||||
@ -180,7 +180,7 @@ func (c *Client) SwapItem() error {
|
||||
|
||||
// Disconnect disconnect the server.
|
||||
// Server will close the connection.
|
||||
func (c *Client) Disconnect() error {
|
||||
func (c *Client) disconnect() error {
|
||||
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 //皮肤显示
|
||||
MainHand int //主手
|
||||
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,
|
||||
MainHand: 1,
|
||||
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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/Tnze/go-mc/data"
|
||||
"math"
|
||||
|
||||
"github.com/Tnze/go-mc/data/block"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
const maxPaletteBits = 8
|
||||
|
||||
// DecodeChunkColumn decode the chunk data structure.
|
||||
// If decoding went error, successful decoded data will be returned.
|
||||
func DecodeChunkColumn(mask int32, data []byte) (*Chunk, error) {
|
||||
@ -26,35 +30,35 @@ func DecodeChunkColumn(mask int32, data []byte) (*Chunk, error) {
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func perBits(BitsPerBlock byte) int {
|
||||
func perBits(bpb byte) uint {
|
||||
switch {
|
||||
case BitsPerBlock <= 4:
|
||||
case bpb <= 4:
|
||||
return 4
|
||||
case BitsPerBlock < 9:
|
||||
return int(BitsPerBlock)
|
||||
case bpb <= maxPaletteBits:
|
||||
return uint(bpb)
|
||||
default:
|
||||
return data.BitsPerBlock // DefaultBitsPerBlock
|
||||
return uint(block.BitsPerBlock)
|
||||
}
|
||||
}
|
||||
|
||||
func readSection(data pk.DecodeReader) (s Section, err error) {
|
||||
var BlockCount pk.Short
|
||||
if err := BlockCount.Decode(data); err != nil {
|
||||
return nil, fmt.Errorf("read block count error: %w", err)
|
||||
var nonAirBlockCount pk.Short
|
||||
if err := nonAirBlockCount.Decode(data); err != nil {
|
||||
return nil, fmt.Errorf("block count: %w", err)
|
||||
}
|
||||
var bpb pk.UnsignedByte
|
||||
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.
|
||||
// Otherwise use paletteSection.
|
||||
var palettes []BlockStatus
|
||||
var palettesIndex map[BlockStatus]int
|
||||
if bpb < 9 {
|
||||
if bpb <= maxPaletteBits {
|
||||
// read palettes
|
||||
var length pk.VarInt
|
||||
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)
|
||||
palettesIndex = make(map[BlockStatus]int, length)
|
||||
@ -85,8 +89,15 @@ func readSection(data pk.DecodeReader) (s Section, err error) {
|
||||
dataArray[i] = uint64(v)
|
||||
}
|
||||
|
||||
sec := directSection{bpb: perBits(byte(bpb)), data: dataArray}
|
||||
if bpb < 9 {
|
||||
width := perBits(byte(bpb))
|
||||
sec := directSection{
|
||||
bitArray{
|
||||
width: width,
|
||||
valsPerElement: valsPerBitArrayElement(width),
|
||||
data: dataArray,
|
||||
},
|
||||
}
|
||||
if bpb <= maxPaletteBits {
|
||||
return &paletteSection{
|
||||
palette: palettes,
|
||||
palettesIndex: palettesIndex,
|
||||
@ -98,46 +109,38 @@ func readSection(data pk.DecodeReader) (s Section, err error) {
|
||||
}
|
||||
|
||||
type directSection struct {
|
||||
bpb int
|
||||
data []uint64
|
||||
bitArray
|
||||
}
|
||||
|
||||
func (d *directSection) GetBlock(offset int) BlockStatus {
|
||||
offset *= d.bpb
|
||||
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) GetBlock(offset uint) BlockStatus {
|
||||
return BlockStatus(d.Get(offset))
|
||||
}
|
||||
|
||||
func (d *directSection) SetBlock(offset int, s BlockStatus) {
|
||||
offset *= d.bpb
|
||||
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) SetBlock(offset uint, s BlockStatus) {
|
||||
d.Set(offset, uint(s))
|
||||
}
|
||||
|
||||
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 {
|
||||
newSection := &directSection{
|
||||
bpb: bpb,
|
||||
data: make([]uint64, 16*16*16*bpb/64),
|
||||
func (d *directSection) clone(bpb uint) *directSection {
|
||||
out := newSectionWithSize(bpb)
|
||||
for offset := uint(0); offset < 16*16*16; offset++ {
|
||||
out.SetBlock(offset, d.GetBlock(offset))
|
||||
}
|
||||
for offset := 0; offset < 16*16*16; offset++ {
|
||||
newSection.SetBlock(offset, d.GetBlock(offset))
|
||||
return out
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -146,12 +149,12 @@ type paletteSection struct {
|
||||
directSection
|
||||
}
|
||||
|
||||
func (p *paletteSection) GetBlock(offset int) BlockStatus {
|
||||
func (p *paletteSection) GetBlock(offset uint) BlockStatus {
|
||||
v := p.directSection.GetBlock(offset)
|
||||
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 {
|
||||
p.directSection.SetBlock(offset, BlockStatus(i))
|
||||
return
|
||||
@ -163,7 +166,7 @@ func (p *paletteSection) SetBlock(offset int, s BlockStatus) {
|
||||
// Increase the underlying directSection
|
||||
// Suppose that old bpb fit len(p.palette) before it appended.
|
||||
// So bpb+1 must enough for new len(p.palette).
|
||||
p.directSection = *p.directSection.clone(p.bpb + 1)
|
||||
p.directSection = *p.directSection.clone(p.width + 1)
|
||||
}
|
||||
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
|
||||
|
||||
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"
|
||||
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 {
|
||||
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
|
||||
type Slot struct {
|
||||
Present bool
|
||||
ItemID int32
|
||||
ItemID item.ID
|
||||
Count int8
|
||||
NBT interface{}
|
||||
}
|
||||
@ -25,9 +58,11 @@ func (s *Slot) Decode(r pk.DecodeReader) error {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
s.ItemID = item.ID(itemID)
|
||||
if err := (*pk.Byte)(&s.Count).Decode(r); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -38,6 +73,25 @@ func (s *Slot) Decode(r pk.DecodeReader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Slot) String() string {
|
||||
return data.ItemNameByID[s.ItemID]
|
||||
func (s Slot) Encode() []byte {
|
||||
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"
|
||||
|
||||
type Pos struct {
|
||||
X, Y, Z float64
|
||||
Yaw, Pitch float32
|
||||
OnGround bool
|
||||
}
|
||||
|
||||
func (p Pos) PosEqual(other Pos) bool {
|
||||
return p.X == other.X && p.Y == other.Y && p.Z == other.Z
|
||||
}
|
||||
func (p Pos) LookEqual(other Pos) bool {
|
||||
return p.Yaw == other.Yaw && p.Pitch == other.Pitch
|
||||
}
|
||||
func (p Pos) Equal(other Pos) bool {
|
||||
return p.PosEqual(other) && p.LookEqual(other) && p.OnGround == other.OnGround
|
||||
}
|
||||
|
||||
// Player includes the player's status.
|
||||
type Player struct {
|
||||
entity.Entity
|
||||
UUID [2]int64 //128bit UUID
|
||||
|
||||
X, Y, Z float64
|
||||
Yaw, Pitch float32
|
||||
OnGround bool
|
||||
Pos Pos
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/Tnze/go-mc/bot/world/entity"
|
||||
)
|
||||
|
||||
// World record all of the things in the world where player at
|
||||
type World struct {
|
||||
Entities map[int32]entity.Entity
|
||||
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
|
||||
entityLock sync.RWMutex
|
||||
Entities map[int32]*entity.Entity
|
||||
chunkLock sync.RWMutex
|
||||
Chunks map[ChunkLoc]*Chunk
|
||||
}
|
||||
|
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
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/Tnze/go-mc/bot"
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
_ "github.com/Tnze/go-mc/data/lang/en-us"
|
||||
|
@ -2,9 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/google/uuid"
|
||||
"log"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/Tnze/go-mc/bot"
|
||||
"github.com/Tnze/go-mc/chat"
|
||||
_ "github.com/Tnze/go-mc/data/lang/en-us"
|
||||
|
@ -3,8 +3,9 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/Tnze/go-mc/yggdrasil"
|
||||
"os"
|
||||
|
||||
"github.com/Tnze/go-mc/yggdrasil"
|
||||
)
|
||||
|
||||
var user = flag.String("user", "", "Can be an email address or player name for unmigrated accounts")
|
||||
|
@ -2,12 +2,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/Tnze/go-mc/bot"
|
||||
"github.com/Tnze/go-mc/data"
|
||||
"github.com/Tnze/go-mc/net"
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
"github.com/google/uuid"
|
||||
"log"
|
||||
)
|
||||
|
||||
const ProtocolVersion = 578
|
||||
@ -64,7 +65,7 @@ func handlePlaying(conn net.Conn, protocol int32) {
|
||||
}
|
||||
|
||||
joinGame(conn)
|
||||
conn.WritePacket(pk.Marshal(data.PlayerPositionAndLookClientbound,
|
||||
conn.WritePacket(pk.Marshal(data.PositionClientbound,
|
||||
// https://wiki.vg/Protocol#Player_Position_And_Look_.28clientbound.29
|
||||
pk.Double(0), pk.Double(0), pk.Double(0), // XYZ
|
||||
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 {
|
||||
return conn.WritePacket(pk.Marshal(data.JoinGame,
|
||||
return conn.WritePacket(pk.Marshal(data.Login,
|
||||
pk.Int(0), // EntityID
|
||||
pk.UnsignedByte(1), // Gamemode
|
||||
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
|
||||
|
||||
// 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 (
|
||||
SpawnObject int32 = iota //0x00
|
||||
SpawnExperienceOrb
|
||||
SpawnLivingEntity
|
||||
SpawnPainting
|
||||
SpawnPlayer
|
||||
EntityAnimationClientbound
|
||||
Statistics
|
||||
AcknowledgePlayerDigging
|
||||
BlockBreakAnimation
|
||||
BlockEntityData
|
||||
BlockAction
|
||||
BlockChange
|
||||
BossBar
|
||||
ServerDifficulty
|
||||
ChatMessageClientbound
|
||||
TabComplete
|
||||
// Clientbound packets for connections in the login state.
|
||||
EncryptionBeginClientbound PktID = 0x01
|
||||
Success PktID = 0x02
|
||||
Compress PktID = 0x03
|
||||
LoginPluginRequest PktID = 0x04
|
||||
Disconnect PktID = 0x00
|
||||
// Serverbound packets for connections in the login state.
|
||||
EncryptionBeginServerbound PktID = 0x01
|
||||
LoginPluginResponse PktID = 0x02
|
||||
LoginStart PktID = 0x00
|
||||
|
||||
DeclareCommands //0x10
|
||||
WindowConfirmationClientbound
|
||||
CloseWindowClientbound
|
||||
WindowItems
|
||||
WindowProperty
|
||||
SetSlot
|
||||
SetCooldown
|
||||
PluginMessageClientbound
|
||||
NamedSoundEffect
|
||||
DisconnectPlay
|
||||
EntityStatus
|
||||
Explosion
|
||||
UnloadChunk
|
||||
ChangeGameState
|
||||
OpenHorseWindow
|
||||
KeepAliveClientbound
|
||||
// Clientbound packets for connections in the play state.
|
||||
EntityMetadata PktID = 0x44
|
||||
Teams PktID = 0x4c
|
||||
BossBar PktID = 0x0c
|
||||
Map PktID = 0x25
|
||||
Difficulty PktID = 0x0d
|
||||
Camera PktID = 0x3e
|
||||
WindowItems PktID = 0x13
|
||||
ScoreboardObjective PktID = 0x4a
|
||||
RelEntityMove PktID = 0x27
|
||||
DeclareCommands PktID = 0x10
|
||||
CombatEvent PktID = 0x31
|
||||
SpawnEntityPainting PktID = 0x03
|
||||
EntityMoveLook PktID = 0x28
|
||||
ScoreboardScore PktID = 0x4d
|
||||
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
|
||||
Effect
|
||||
Particle
|
||||
UpdateLight
|
||||
JoinGame
|
||||
MapData
|
||||
TradeList
|
||||
EntityRelativeMove
|
||||
EntityLookAndRelativeMove
|
||||
EntityLook
|
||||
Entity
|
||||
VehicleMoveClientbound
|
||||
OpenBook
|
||||
OpenWindow
|
||||
OpenSignEditor
|
||||
CraftRecipeResponse
|
||||
// Clientbound packets used to respond to ping/status requests.
|
||||
ServerInfo PktID = 0x00
|
||||
PingClientbound PktID = 0x01
|
||||
// Serverbound packets used to ping or read server status.
|
||||
PingStart PktID = 0x00
|
||||
PingServerbound PktID = 0x01
|
||||
|
||||
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
|
||||
|
||||
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/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"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/Tnze/go-mc/data"
|
||||
)
|
||||
|
||||
// Packet define a net data package
|
||||
@ -14,8 +16,8 @@ type Packet struct {
|
||||
}
|
||||
|
||||
//Marshal generate Packet with the ID and Fields
|
||||
func Marshal(ID int32, fields ...FieldEncoder) (pk Packet) {
|
||||
pk.ID = ID
|
||||
func Marshal(id data.PktID, fields ...FieldEncoder) (pk Packet) {
|
||||
pk.ID = int32(id)
|
||||
|
||||
for _, v := range fields {
|
||||
pk.Data = append(pk.Data, v.Encode()...)
|
||||
|
@ -2,10 +2,11 @@ package packet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/google/uuid"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/Tnze/go-mc/nbt"
|
||||
)
|
||||
|
||||
@ -364,6 +365,16 @@ func (p *Position) Decode(r DecodeReader) error {
|
||||
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
|
||||
func (f Float) Encode() []byte {
|
||||
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