package world import ( "container/list" "context" "errors" "sync" "github.com/go-gl/mathgl/mgl64" "golang.org/x/exp/constraints" "git.konjactw.dev/falloutBot/go-mc/data/entity" "git.konjactw.dev/falloutBot/go-mc/level" "git.konjactw.dev/falloutBot/go-mc/level/block" pk "git.konjactw.dev/falloutBot/go-mc/net/packet" "git.konjactw.dev/patyhank/minego/pkg/bot" "git.konjactw.dev/patyhank/minego/pkg/protocol" "git.konjactw.dev/patyhank/minego/pkg/protocol/metadata" cp "git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/client" "git.konjactw.dev/patyhank/minego/pkg/protocol/slot" ) type World struct { c bot.Client columns map[level.ChunkPos]*level.Chunk entities map[int32]*Entity entityLock sync.Mutex chunkLock sync.Mutex } func NewWorld(c bot.Client) *World { w := &World{ c: c, columns: make(map[level.ChunkPos]*level.Chunk), entities: make(map[int32]*Entity), } bot.AddHandler(c, func(ctx context.Context, p *cp.LevelChunkWithLight) { w.chunkLock.Lock() defer w.chunkLock.Unlock() w.columns[p.Pos] = p.Data }) bot.AddHandler(c, func(ctx context.Context, p *cp.ForgetLevelChunk) { w.chunkLock.Lock() defer w.chunkLock.Unlock() delete(w.columns, p.Pos) }) bot.AddHandler(c, func(ctx context.Context, p *cp.Respawn) { w.chunkLock.Lock() defer w.chunkLock.Unlock() w.columns = make(map[level.ChunkPos]*level.Chunk) }) bot.AddHandler(c, func(ctx context.Context, p *cp.AddEntity) { w.entityLock.Lock() defer w.entityLock.Unlock() w.entities[p.ID] = &Entity{ id: p.ID, entityUUID: p.UUID, entityType: entity.ID(p.Type), pos: mgl64.Vec3{p.X, p.Y, p.Z}, rot: mgl64.Vec2{pk.Angle(p.XRot).ToDeg(), pk.Angle(p.YRot).ToDeg()}, metadata: nil, equipment: nil, } }) bot.AddHandler(c, func(ctx context.Context, p *cp.RemoveEntities) { w.entityLock.Lock() defer w.entityLock.Unlock() for _, d := range p.EntityIDs { e, ok := w.entities[d] if ok { bot.PublishEvent(c, EntityRemoveEvent{Entity: e}) delete(w.entities, d) } } }) bot.AddHandler(c, func(ctx context.Context, p *cp.SetEntityMetadata) { w.entityLock.Lock() defer w.entityLock.Unlock() e, ok := w.entities[p.EntityID] if ok { if e.metadata == nil { e.metadata = make(map[uint8]metadata.Metadata) } for u, entityMetadata := range p.Metadata.Data { e.metadata[u] = entityMetadata } } }) bot.AddHandler(c, func(ctx context.Context, p *cp.SetEquipment) { w.entityLock.Lock() defer w.entityLock.Unlock() e, ok := w.entities[p.EntityID] if ok { if e.equipment == nil { e.equipment = make(map[int8]slot.Slot) } for _, equipment := range p.Equipment { e.equipment[equipment.Slot] = equipment.Item } } }) bot.AddHandler(c, func(ctx context.Context, p *cp.UpdateEntityPosition) { w.entityLock.Lock() defer w.entityLock.Unlock() if e, ok := w.entities[p.EntityID]; ok { e.pos = e.pos.Add(mgl64.Vec3{float64(p.DeltaX) / 4096.0, float64(p.DeltaY) / 4096.0, float64(p.DeltaZ) / 4096.0}) } }) bot.AddHandler(c, func(ctx context.Context, p *cp.UpdateEntityRotation) { w.entityLock.Lock() defer w.entityLock.Unlock() if e, ok := w.entities[p.EntityID]; ok { e.rot = mgl64.Vec2{float64(p.Yaw), float64(p.Pitch)} } }) bot.AddHandler(c, func(ctx context.Context, p *cp.UpdateEntityPositionAndRotation) { w.entityLock.Lock() defer w.entityLock.Unlock() if e, ok := w.entities[p.EntityID]; ok { e.pos = e.pos.Add(mgl64.Vec3{float64(p.DeltaX) / 4096.0, float64(p.DeltaY) / 4096.0, float64(p.DeltaZ) / 4096.0}) } }) return w } func (w *World) GetBlock(pos protocol.Position) (block.Block, error) { w.chunkLock.Lock() defer w.chunkLock.Unlock() chunkX := pos[0] >> 4 chunkZ := pos[2] >> 4 pos2d := level.ChunkPos{chunkX, chunkZ} chunk, ok := w.columns[pos2d] if !ok { return nil, errors.New("chunk not loaded") } blockX := pos[0] & 15 blockZ := pos[2] & 15 blockY := pos[1] & 15 blockIdx := (blockY << 8) | (blockZ << 4) | blockX sectionY := pos[1] >> 4 if sectionY < 0 || int(sectionY) >= len(chunk.Sections) { return nil, errors.New("invalid section Y coordinate") } blockStateId := chunk.Sections[sectionY].GetBlock(int(blockIdx)) return block.StateList[blockStateId], nil } func (w *World) SetBlock(pos protocol.Position, blk block.Block) error { w.chunkLock.Lock() defer w.chunkLock.Unlock() chunkX := pos[0] >> 4 chunkZ := pos[2] >> 4 pos2d := level.ChunkPos{chunkX, chunkZ} chunk, ok := w.columns[pos2d] if !ok { return errors.New("chunk not loaded") } blockX := pos[0] & 15 blockZ := pos[2] & 15 sectionY := pos[1] >> 4 blockY := pos[1] & 15 if sectionY < 0 || int(sectionY) >= len(chunk.Sections) { return errors.New("invalid section Y coordinate") } section := chunk.Sections[sectionY] blockIdx := (blockY << 8) | (blockZ << 4) | blockX section.SetBlock(int(blockIdx), block.ToStateID[blk]) return nil } func (w *World) GetNearbyBlocks(pos protocol.Position, radius int32) ([]block.Block, error) { var blocks []block.Block for dx := -radius; dx <= radius; dx++ { for dy := -radius; dy <= radius; dy++ { for dz := -radius; dz <= radius; dz++ { blk, err := w.GetBlock(protocol.Position{pos[0] + dx, pos[1] + dy, pos[2] + dz}) if err != nil { continue } blocks = append(blocks, blk) } } } return blocks, nil } func (w *World) FindNearbyBlock(pos protocol.Position, radius int32, blk block.Block) (protocol.Position, error) { visited := make(map[protocol.Position]bool) queue := list.New() start := pos queue.PushBack(start) visited[start] = true // Direction vectors for 6-way adjacent blocks dirs := []protocol.Position{ {1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}, } for queue.Len() > 0 { current := queue.Remove(queue.Front()).(protocol.Position) // Skip if beyond the radius if abs(current[0]-pos[0]) > radius || abs(current[1]-pos[1]) > radius || abs(current[2]-pos[2]) > radius { continue } // Check if current block matches target if currentBlock, err := w.GetBlock(current); err == nil { if currentBlock == blk { return current, nil } } // Check all 6 adjacent blocks for _, dir := range dirs { next := protocol.Position{ current[0] + dir[0], current[1] + dir[1], current[2] + dir[2], } if !visited[next] { visited[next] = true queue.PushBack(next) } } } return protocol.Position{}, errors.New("block not found") } func (w *World) Entities() []bot.Entity { w.entityLock.Lock() defer w.entityLock.Unlock() var entities []bot.Entity for _, e := range w.entities { entities = append(entities, e) } return entities } func (w *World) GetEntity(id int32) bot.Entity { w.entityLock.Lock() defer w.entityLock.Unlock() return w.entities[id] } func (w *World) GetNearbyEntities(radius int32) []bot.Entity { w.entityLock.Lock() defer w.entityLock.Unlock() selfPos := w.c.Player().Entity().Position() var entities []bot.Entity for _, e := range w.entities { sqr := e.pos.Sub(selfPos).LenSqr() if sqr <= float64(radius*radius) { entities = append(entities, e) } } return entities } func (w *World) GetEntitiesByType(entityType entity.ID) []bot.Entity { w.entityLock.Lock() defer w.entityLock.Unlock() var entities []bot.Entity for _, e := range w.entities { if e.entityType == entityType { entities = append(entities, e) } } return entities } func abs[T constraints.Signed | constraints.Float](x T) T { if x < 0 { return -x } return x }