add world module for in-memory block storage and integrate getblock command in messenger
This commit is contained in:
@@ -2,11 +2,16 @@ package messenger
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/admin-else/strom/cmd/strom/cmd_util"
|
||||
"github.com/admin-else/strom/mc/bot/chat"
|
||||
"github.com/admin-else/strom/mc/bot/keepalive"
|
||||
"github.com/admin-else/strom/mc/bot/world"
|
||||
"github.com/admin-else/strom/mc/client"
|
||||
"github.com/admin-else/strom/mc/data"
|
||||
"github.com/admin-else/strom/mc/event"
|
||||
"github.com/admin-else/strom/mc/proto"
|
||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||
@@ -20,7 +25,8 @@ var (
|
||||
|
||||
type Messenger struct {
|
||||
*proto.Conn
|
||||
chat *chat.Module
|
||||
chat *chat.Module
|
||||
world *world.Module
|
||||
}
|
||||
|
||||
func (m *Messenger) OnChat(e *v1_21_11.PlayToClientPacketPlayerChat) (err error) {
|
||||
@@ -39,7 +45,46 @@ func (m *Messenger) OnChatSystem(e *v1_21_11.PlayToClientPacketSystemChat) (err
|
||||
}
|
||||
|
||||
func (m *Messenger) OnStdin(e event.Stdin) (err error) {
|
||||
return m.chat.SendMessage(e.Val)
|
||||
input := strings.TrimSpace(e.Val)
|
||||
if strings.HasPrefix(input, ">say ") {
|
||||
return m.chat.SendMessage(strings.TrimSpace(strings.TrimPrefix(input, ">say")))
|
||||
}
|
||||
if strings.HasPrefix(input, ">getblock ") {
|
||||
return m.handleGetBlock(strings.TrimSpace(strings.TrimPrefix(input, ">getblock")))
|
||||
}
|
||||
return m.chat.SendMessage(input)
|
||||
}
|
||||
|
||||
func (m *Messenger) handleGetBlock(args string) (err error) {
|
||||
fields := strings.Fields(args)
|
||||
if len(fields) != 3 {
|
||||
m.Log.Info("getblock", "error", "expected >getblock <x> <y> <z>")
|
||||
return nil
|
||||
}
|
||||
|
||||
coords := make([]int64, 3)
|
||||
for i, f := range fields {
|
||||
coords[i], err = strconv.ParseInt(f, 10, 32)
|
||||
if err != nil {
|
||||
m.Log.Info("getblock", "error", fmt.Sprintf("bad coordinate %q", f))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
stateId, err := m.world.GetBlock(int32(coords[0]), int32(coords[1]), int32(coords[2]))
|
||||
if err != nil {
|
||||
m.Log.Info("getblock", "error", err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
block, ok := data.LookupBlockByStateId(m.Version, stateId)
|
||||
if !ok {
|
||||
m.Log.Info("getblock", "stateId", stateId, "name", "unknown")
|
||||
return nil
|
||||
}
|
||||
|
||||
m.Log.Info("getblock", "x", coords[0], "y", coords[1], "z", coords[2], "name", block.Name, "stateId", stateId)
|
||||
return nil
|
||||
}
|
||||
|
||||
func Run(args []string) (err error) {
|
||||
@@ -62,9 +107,14 @@ func Run(args []string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Shareable world storage. Other bots can subscribe to the same World.
|
||||
w := world.NewWorld(c.Version, -64, 384)
|
||||
|
||||
m := &Messenger{
|
||||
Conn: c,
|
||||
chat: chatMod,
|
||||
Conn: c,
|
||||
chat: chatMod,
|
||||
world: world.Start(c, w),
|
||||
}
|
||||
|
||||
event.StartListingStdin(m.Loop)
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
package world
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/admin-else/strom/mc/level"
|
||||
"github.com/admin-else/strom/mc/proto"
|
||||
"github.com/admin-else/strom/mc/proto_generated/v1_21_11"
|
||||
)
|
||||
|
||||
var (
|
||||
ChunkNotLoadedErr = errors.New("chunk not loaded")
|
||||
OutOfBoundsErr = errors.New("coordinates out of bounds")
|
||||
)
|
||||
|
||||
// ChunkPos identifies a chunk column.
|
||||
type ChunkPos struct {
|
||||
X, Z int32
|
||||
}
|
||||
|
||||
// World stores a shareable in-memory view of the Minecraft world.
|
||||
// Multiple bots can subscribe to the same World and update it from their
|
||||
// respective connections.
|
||||
type World struct {
|
||||
mu sync.RWMutex
|
||||
chunks map[ChunkPos]*level.Chunk
|
||||
version string
|
||||
minY int
|
||||
height int
|
||||
}
|
||||
|
||||
// NewWorld creates a new World for the given protocol version and vertical
|
||||
// bounds. For a 1.21 overworld use minY=-64 and height=384.
|
||||
func NewWorld(version string, minY, height int) *World {
|
||||
return &World{
|
||||
chunks: make(map[ChunkPos]*level.Chunk),
|
||||
version: version,
|
||||
minY: minY,
|
||||
height: height,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBlock returns the global block state ID at the given world coordinates.
|
||||
func (w *World) GetBlock(x, y, z int32) (stateId int32, err error) {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.getBlockLocked(x, y, z)
|
||||
}
|
||||
|
||||
func (w *World) getBlockLocked(x, y, z int32) (stateId int32, err error) {
|
||||
chunkX := floorDiv(x, level.ChunkWidth)
|
||||
chunkZ := floorDiv(z, level.ChunkWidth)
|
||||
chunk, ok := w.chunks[ChunkPos{chunkX, chunkZ}]
|
||||
if !ok {
|
||||
err = ChunkNotLoadedErr
|
||||
return
|
||||
}
|
||||
|
||||
sectionIndex := (int(y) - w.minY) / level.ChunkWidth
|
||||
if sectionIndex < 0 || sectionIndex >= len(chunk.Sections) {
|
||||
err = OutOfBoundsErr
|
||||
return
|
||||
}
|
||||
|
||||
lx, ly, lz := blockToLocal(x, y, z)
|
||||
index := ly*level.ChunkWidth*level.ChunkWidth + lz*level.ChunkWidth + lx
|
||||
return chunk.Sections[sectionIndex].Blocks.Get(index)
|
||||
}
|
||||
|
||||
// SetBlock updates the global block state ID at the given world coordinates.
|
||||
func (w *World) SetBlock(x, y, z int32, stateId int32) (err error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
chunkX := floorDiv(x, level.ChunkWidth)
|
||||
chunkZ := floorDiv(z, level.ChunkWidth)
|
||||
chunk, ok := w.chunks[ChunkPos{chunkX, chunkZ}]
|
||||
if !ok {
|
||||
return ChunkNotLoadedErr
|
||||
}
|
||||
|
||||
sectionIndex := (int(y) - w.minY) / level.ChunkWidth
|
||||
if sectionIndex < 0 || sectionIndex >= len(chunk.Sections) {
|
||||
return OutOfBoundsErr
|
||||
}
|
||||
|
||||
lx, ly, lz := blockToLocal(x, y, z)
|
||||
index := ly*level.ChunkWidth*level.ChunkWidth + lz*level.ChunkWidth + lx
|
||||
return chunk.Sections[sectionIndex].Blocks.Set(index, stateId)
|
||||
}
|
||||
|
||||
// Module subscribes to chunk and block-change packets and updates a shared
|
||||
// World. Multiple Modules can point to the same World.
|
||||
type Module struct {
|
||||
*proto.Conn
|
||||
world *World
|
||||
}
|
||||
|
||||
// GetBlock returns the block state ID at the given world coordinates via the
|
||||
// shared World.
|
||||
func (m *Module) GetBlock(x, y, z int32) (int32, error) {
|
||||
return m.world.GetBlock(x, y, z)
|
||||
}
|
||||
|
||||
// Start registers packet handlers on c that update the shared World w.
|
||||
func Start(c *proto.Conn, w *World) *Module {
|
||||
m := &Module{Conn: c, world: w}
|
||||
m.RegisterUntilLatest(m.onMapChunk)
|
||||
m.RegisterUntilLatest(m.onBlockChange)
|
||||
m.RegisterUntilLatest(m.onMultiBlockChange)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Module) onMapChunk(p *v1_21_11.PlayToClientPacketMapChunk) (err error) {
|
||||
chunk, err := level.ReadChunkFromChunkPacketData(
|
||||
bytes.NewReader(p.ChunkData.Val),
|
||||
m.world.version,
|
||||
m.world.height,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.world.mu.Lock()
|
||||
m.world.chunks[ChunkPos{p.X, p.Z}] = chunk
|
||||
m.world.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) onBlockChange(p *v1_21_11.PlayToClientPacketBlockChange) (err error) {
|
||||
return m.world.SetBlock(p.Location.X, int32(p.Location.Y), p.Location.Z, p.Type)
|
||||
}
|
||||
|
||||
func (m *Module) onMultiBlockChange(p *v1_21_11.PlayToClientPacketMultiBlockChange) (err error) {
|
||||
chunkX := p.ChunkCoordinates.X
|
||||
chunkZ := p.ChunkCoordinates.Z
|
||||
sectionY := p.ChunkCoordinates.Y
|
||||
|
||||
for _, record := range p.Records {
|
||||
packedPos := record & 0xFFF
|
||||
stateId := record >> 12
|
||||
|
||||
localX := (packedPos >> 8) & 0xF
|
||||
localY := (packedPos >> 4) & 0xF
|
||||
localZ := packedPos & 0xF
|
||||
|
||||
x := chunkX*level.ChunkWidth + int32(localX)
|
||||
y := sectionY*level.ChunkWidth + int32(localY)
|
||||
z := chunkZ*level.ChunkWidth + int32(localZ)
|
||||
|
||||
if err = m.world.SetBlock(x, y, z, stateId); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func floorDiv(a, b int32) int32 {
|
||||
return (a - ((a%b + b) % b)) / b
|
||||
}
|
||||
|
||||
func blockToLocal(x, y, z int32) (lx, ly, lz int32) {
|
||||
lx = x % level.ChunkWidth
|
||||
if lx < 0 {
|
||||
lx += level.ChunkWidth
|
||||
}
|
||||
ly = y % level.ChunkWidth
|
||||
if ly < 0 {
|
||||
ly += level.ChunkWidth
|
||||
}
|
||||
lz = z % level.ChunkWidth
|
||||
if lz < 0 {
|
||||
lz += level.ChunkWidth
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user