diff --git a/bot/client.go b/bot/client.go index 23f7b27..c3e7c4c 100644 --- a/bot/client.go +++ b/bot/client.go @@ -23,10 +23,11 @@ type Client struct { abilities PlayerAbilities settings Settings - Wd world.World //the map data - Inputs phy.Inputs - Physics phy.State - lastPosTx time.Time + Wd world.World //the map data + Inputs phy.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! @@ -58,8 +59,8 @@ func NewClient() *Client { Auth: Auth{Name: "Steve"}, Delegate: make(chan func() error), Wd: world.World{ - Entities: make(map[int32]entity.Entity), - Chunks: make(map[world.ChunkLoc]*world.Chunk), + 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), diff --git a/bot/event.go b/bot/event.go index 44ec23e..42a3b82 100644 --- a/bot/event.go +++ b/bot/event.go @@ -71,6 +71,9 @@ type eventBroker struct { // 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 { diff --git a/bot/ingame.go b/bot/ingame.go index 24808d0..675144a 100644 --- a/bot/ingame.go +++ b/bot/ingame.go @@ -14,7 +14,6 @@ import ( "github.com/Tnze/go-mc/bot/world/entity/player" "github.com/Tnze/go-mc/chat" "github.com/Tnze/go-mc/data" - "github.com/Tnze/go-mc/data/entity" pk "github.com/Tnze/go-mc/net/packet" "github.com/Tnze/go-mc/net/ptypes" ) @@ -38,7 +37,10 @@ func (c *Client) updateServerPos(pos player.Pos) error { pk.Boolean(pos.OnGround), ), ) - case time.Now().Add(-time.Second).After(c.lastPosTx): + } + + if c.justTeleported || time.Now().Add(-time.Second).After(c.lastPosTx) { + c.justTeleported = false c.lastPosTx = time.Now() sendPlayerPositionPacket(c) } @@ -83,6 +85,11 @@ func (c *Client) HandleGame() error { case <-c.closing: return http.ErrServerClosed case <-cTick.C: + if c.Events.PrePhysics != nil { + if err := c.Events.PrePhysics(); err != nil { + return err + } + } if err := c.Physics.Tick(c.Inputs, &c.Wd); err != nil { c.disconnect() return err @@ -209,12 +216,7 @@ func handleSpawnEntityPacket(c *Client, p pk.Packet) error { if err := se.Decode(p); err != nil { return err } - if e := entity.ByID[entity.ID(se.Type)]; e != nil { - fmt.Printf("Spawning %s at (%f,%f,%f)\n", e.DisplayName, se.X, se.Y, se.Z) - } else { - fmt.Printf("SpawnEntity: %+v\n", se) - } - return nil + return c.Wd.OnSpawnEntity(se) } func handleSpawnLivingEntityPacket(c *Client, p pk.Packet) error { @@ -222,12 +224,7 @@ func handleSpawnLivingEntityPacket(c *Client, p pk.Packet) error { if err := se.Decode(p); err != nil { return err } - if e := entity.ByID[entity.ID(se.Type)]; e != nil { - fmt.Printf("Spawning %s at (%f,%f,%f)\n", e.DisplayName, se.X, se.Y, se.Z) - } else { - fmt.Printf("SpawnLivingEntity: %+v\n", se) - } - return nil + return c.Wd.OnSpawnLivingEntity(se) } func handleSpawnPlayerPacket(c *Client, p pk.Packet) error { @@ -235,35 +232,32 @@ func handleSpawnPlayerPacket(c *Client, p pk.Packet) error { if err := se.Decode(p); err != nil { return err } - fmt.Printf("SpawnPlayer: %+v\n", se) - return nil + fmt.Println(se) + return c.Wd.OnSpawnPlayer(se) } func handleEntityPositionPacket(c *Client, p pk.Packet) error { - var se ptypes.EntityPosition - if err := se.Decode(p); err != nil { + var pu ptypes.EntityPosition + if err := pu.Decode(p); err != nil { return err } - // fmt.Printf("EntityPosition: %+v\n", se) - return nil + return c.Wd.OnEntityPosUpdate(pu) } func handleEntityPositionLookPacket(c *Client, p pk.Packet) error { - var se ptypes.EntityPositionLook - if err := se.Decode(p); err != nil { + var epr ptypes.EntityPositionLook + if err := epr.Decode(p); err != nil { return err } - // fmt.Printf("EntityPositionLook: %+v\n", se) - return nil + return c.Wd.OnEntityPosLookUpdate(epr) } func handleEntityLookPacket(c *Client, p pk.Packet) error { - var se ptypes.EntityRotation - if err := se.Decode(p); err != nil { + var er ptypes.EntityRotation + if err := er.Decode(p); err != nil { return err } - // fmt.Printf("EntityRotation: %+v\n", se) - return nil + return c.Wd.OnEntityLookUpdate(er) } func handleEntityMovePacket(c *Client, p pk.Packet) error { @@ -312,8 +306,7 @@ func handleDestroyEntitiesPacket(c *Client, p pk.Packet) error { } } - fmt.Printf("DestroyEntities: %v\n", entities) - return nil + return c.Wd.OnEntityDestroy(entities) } func handleSoundEffect(c *Client, p pk.Packet) error { @@ -481,7 +474,7 @@ func handleJoinGamePacket(c *Client, p pk.Packet) error { return err } - c.EntityID = int(pkt.PlayerEntity) + c.Player.ID = int32(pkt.PlayerEntity) c.Gamemode = int(pkt.Gamemode & 0x7) c.Hardcore = pkt.Gamemode&0x8 != 0 c.Dimension = int(pkt.Dimension) @@ -671,6 +664,7 @@ func handlePlayerPositionAndLookPacket(c *Client, p pk.Packet) error { return err } c.Player.Pos = pp + c.justTeleported = true if c.Events.PositionChange != nil { if err := c.Events.PositionChange(pp); err != nil { @@ -735,7 +729,6 @@ func handleSetExperience(c *Client, p pk.Packet) (err error) { } func sendPlayerPositionAndLookPacket(c *Client) error { - // fmt.Println("PPL") return c.conn.WritePacket(ptypes.PositionAndLookServerbound{ X: pk.Double(c.Pos.X), Y: pk.Double(c.Pos.Y), @@ -747,7 +740,6 @@ func sendPlayerPositionAndLookPacket(c *Client) error { } func sendPlayerPositionPacket(c *Client) error { - // fmt.Println("P") return c.conn.WritePacket(ptypes.Position{ X: pk.Double(c.Pos.X), Y: pk.Double(c.Pos.Y), @@ -757,7 +749,6 @@ func sendPlayerPositionPacket(c *Client) error { } func sendPlayerLookPacket(c *Client) error { - // fmt.Println("L") return c.conn.WritePacket(ptypes.Look{ Yaw: pk.Float(c.Pos.Yaw), Pitch: pk.Float(c.Pos.Pitch), diff --git a/bot/phy/aabb.go b/bot/phy/aabb.go index e20ac4e..513543c 100644 --- a/bot/phy/aabb.go +++ b/bot/phy/aabb.go @@ -10,6 +10,8 @@ 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{ @@ -24,6 +26,8 @@ func (mm MinMax) Extend(delta float64) MinMax { } } +// 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, @@ -31,6 +35,8 @@ func (mm MinMax) Contract(amt float64) MinMax { } } +// 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, @@ -38,6 +44,7 @@ func (mm MinMax) Expand(amt float64) MinMax { } } +// 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, @@ -51,6 +58,8 @@ type AABB struct { 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), @@ -60,6 +69,8 @@ func (bb AABB) Extend(dx, dy, dz float64) AABB { } } +// 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), @@ -69,6 +80,8 @@ func (bb AABB) Contract(x, y, z float64) AABB { } } +// 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), @@ -78,6 +91,8 @@ func (bb AABB) Expand(x, y, z float64) AABB { } } +// 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), diff --git a/bot/phy/inputs.go b/bot/phy/inputs.go index 68a45e5..a4b853c 100644 --- a/bot/phy/inputs.go +++ b/bot/phy/inputs.go @@ -2,7 +2,7 @@ package phy // Inputs describes the desired movements of the player. type Inputs struct { - Yaw, Pitch float32 + Yaw, Pitch float64 ThrottleX, ThrottleZ float64 } diff --git a/bot/phy/phy.go b/bot/phy/phy.go index 4450c77..0d8288b 100644 --- a/bot/phy/phy.go +++ b/bot/phy/phy.go @@ -14,7 +14,8 @@ const ( playerHeight = 1.8 resetVel = 0.003 - yawSpeed = 3.0 + maxYawChange = 33 + maxPitchChange = 22 gravity = 0.08 drag = 0.98 @@ -87,7 +88,12 @@ func (s *State) surroundings(query AABB, w World) Surrounds { return out } -func (s *State) applyInputs(input Inputs, acceleration, inertia float64) { +func (s *State) applyLookInputs(input Inputs) { + errYaw := math.Min(math.Max(input.Yaw-s.Yaw, -maxYawChange), maxYawChange) + s.Yaw += errYaw +} + +func (s *State) applyPosInputs(input Inputs, acceleration, inertia float64) { speed := math.Sqrt(input.ThrottleX*input.ThrottleX + input.ThrottleZ*input.ThrottleZ) if speed < 0.01 { return @@ -111,7 +117,8 @@ func (s *State) Tick(input Inputs, w World) error { inertia *= slipperiness acceleration = 0.1 * (0.1627714 / (inertia * inertia * inertia)) } - s.applyInputs(input, acceleration, inertia) + s.applyLookInputs(input) + s.applyPosInputs(input, acceleration, inertia) // Deadzone velocities when they get too low. if math.Abs(s.Vel.X) < resetVel { diff --git a/bot/world/entity/entity.go b/bot/world/entity/entity.go index ee15c08..1744c1f 100644 --- a/bot/world/entity/entity.go +++ b/bot/world/entity/entity.go @@ -2,13 +2,27 @@ package entity import ( "github.com/Tnze/go-mc/data" + "github.com/Tnze/go-mc/data/entity" "github.com/Tnze/go-mc/nbt" pk "github.com/Tnze/go-mc/net/packet" + "github.com/google/uuid" ) -//Entity is the entity of minecraft +//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 diff --git a/bot/world/world.go b/bot/world/world.go index 31625ad..9089233 100644 --- a/bot/world/world.go +++ b/bot/world/world.go @@ -1,120 +1,15 @@ package world import ( + "sync" + "github.com/Tnze/go-mc/bot/world/entity" - "github.com/Tnze/go-mc/data/block" - pk "github.com/Tnze/go-mc/net/packet" ) // 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 uint) BlockStatus - // SetBlock is the reverse operation of GetBlock. - SetBlock(offset uint, s BlockStatus) -} - -func SectionIdx(x, y, z int) uint { - // 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 uint(((y & 15) << 8) | (z << 4) | x) -} - -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 && y >= 0 { - if sec := c.Sections[y>>4]; sec != nil { - return sec.GetBlock(SectionIdx(x&15, y&15, z&15)) - } - } - return 0 -} - -func (w *World) UnloadChunk(loc ChunkLoc) { - delete(w.Chunks, loc) -} - -func (w *World) UnaryBlockUpdate(pos pk.Position, bStateID BlockStatus) bool { - 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 { - sec.SetBlock(bIdx, bStateID) - } - return true -} - -func (w *World) MultiBlockUpdate(loc ChunkLoc, sectionY int, blocks []pk.VarLong) bool { - 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 { - bStateID := b >> 12 - x, z, y := (b>>8)&0xf, (b>>4)&0xf, b&0xf - sec.SetBlock(SectionIdx(int(x&15), int(y&15), int(z&15)), BlockStatus(bStateID)) - } - - return true -} - -// 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 } diff --git a/bot/world/world_chunk.go b/bot/world/world_chunk.go new file mode 100644 index 0000000..691f791 --- /dev/null +++ b/bot/world/world_chunk.go @@ -0,0 +1,106 @@ +package world + +import ( + "github.com/Tnze/go-mc/data/block" + pk "github.com/Tnze/go-mc/net/packet" +) + +// Chunk store a 256*16*16 area of blocks, sharded on the Y axis into 16 +// sections. +type Chunk struct { + Sections [16]Section +} + +// 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 +} + +// 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 { + 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 { + bStateID := b >> 12 + x, z, y := (b>>8)&0xf, (b>>4)&0xf, b&0xf + sec.SetBlock(sectionIdx(int(x&15), int(y&15), int(z&15)), BlockStatus(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() +} diff --git a/bot/world/world_entity.go b/bot/world/world_entity.go new file mode 100644 index 0000000..79e454b --- /dev/null +++ b/bot/world/world_entity.go @@ -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 +} diff --git a/net/ptypes/motion.go b/net/ptypes/motion.go index eacfc4e..da9463c 100644 --- a/net/ptypes/motion.go +++ b/net/ptypes/motion.go @@ -79,7 +79,7 @@ type Look struct { func (p Look) Encode() pk.Packet { return pk.Marshal( - data.PositionLook, + data.Look, pk.Float(p.Yaw), pk.Float(p.Pitch), pk.Boolean(p.OnGround),