bedrocktool/subcommands/world/map_item.go

232 lines
5.5 KiB
Go
Raw Normal View History

2022-09-04 14:26:32 +00:00
package world
2022-07-29 16:12:06 +00:00
import (
2022-08-20 12:13:56 +00:00
"bytes"
2022-07-29 16:12:06 +00:00
"image"
2022-08-20 12:13:56 +00:00
"image/draw"
2022-08-12 02:53:43 +00:00
"math"
2022-08-20 12:13:56 +00:00
"os"
2022-09-04 11:13:45 +00:00
"time"
2022-07-29 16:12:06 +00:00
2022-09-04 14:53:21 +00:00
"github.com/bedrock-tool/bedrocktool/utils"
2022-09-05 19:08:57 +00:00
"golang.design/x/lockfree"
2022-09-04 14:26:32 +00:00
2022-08-12 16:54:29 +00:00
"github.com/df-mc/dragonfly/server/world/chunk"
2022-07-29 16:12:06 +00:00
"github.com/sandertv/gophertunnel/minecraft/protocol"
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
2022-09-04 11:13:45 +00:00
"github.com/sirupsen/logrus"
2022-08-20 12:13:56 +00:00
"golang.org/x/image/bmp"
2022-07-29 16:12:06 +00:00
)
const VIEW_MAP_ID = 0x424242
2022-08-17 18:04:13 +00:00
// packet to tell the client that it has a map with id 0x424242 in the offhand
2022-07-29 16:12:06 +00:00
var MAP_ITEM_PACKET packet.InventoryContent = packet.InventoryContent{
WindowID: 119,
Content: []protocol.ItemInstance{
{
StackNetworkID: 1,
Stack: protocol.ItemStack{
ItemType: protocol.ItemType{
NetworkID: 420,
MetadataValue: 0,
},
BlockRuntimeID: 0,
Count: 1,
NBTData: map[string]interface{}{
"map_uuid": int64(VIEW_MAP_ID),
},
},
},
},
}
2022-09-05 15:40:03 +00:00
type RenderElem struct {
pos protocol.ChunkPos
ch *chunk.Chunk
}
2022-08-12 16:54:29 +00:00
type MapUI struct {
2022-09-05 19:08:57 +00:00
img *image.RGBA // rendered image
zoomLevel int // pixels per chunk
renderQueue *lockfree.Queue
renderedChunks map[protocol.ChunkPos]*image.RGBA // prerendered chunks
needRedraw bool // when the map has updated this is true
2022-09-04 11:13:45 +00:00
ticker *time.Ticker
w *WorldState
2022-08-12 16:54:29 +00:00
}
2022-09-05 15:40:03 +00:00
func NewMapUI(w *WorldState) *MapUI {
m := &MapUI{
2022-09-05 19:08:57 +00:00
img: image.NewRGBA(image.Rect(0, 0, 128, 128)),
zoomLevel: 16,
renderQueue: lockfree.NewQueue(),
renderedChunks: make(map[protocol.ChunkPos]*image.RGBA),
needRedraw: true,
w: w,
2022-09-04 11:13:45 +00:00
}
2022-09-05 15:40:03 +00:00
return m
2022-09-04 11:13:45 +00:00
}
func (m *MapUI) Start() {
2022-09-07 12:07:33 +00:00
m.ticker = time.NewTicker(33 * time.Millisecond)
2022-09-04 11:13:45 +00:00
go func() {
for range m.ticker.C {
if m.needRedraw {
m.needRedraw = false
m.Redraw()
2022-09-04 14:26:32 +00:00
if m.w.proxy.Client != nil {
if err := m.w.proxy.Client.WritePacket(&packet.ClientBoundMapItemData{
2022-09-04 11:13:45 +00:00
MapID: VIEW_MAP_ID,
Width: 128,
Height: 128,
2022-09-04 14:26:32 +00:00
Pixels: utils.Img2rgba(m.img),
2022-09-04 11:13:45 +00:00
UpdateFlags: 2,
}); err != nil {
logrus.Error(err)
return
}
}
}
}
}()
}
func (m *MapUI) Stop() {
if m.ticker != nil {
m.ticker.Stop()
2022-08-12 16:54:29 +00:00
}
}
2022-08-17 18:04:13 +00:00
// Reset resets the map to inital state
2022-08-12 16:54:29 +00:00
func (m *MapUI) Reset() {
2022-09-05 19:08:57 +00:00
m.renderedChunks = make(map[protocol.ChunkPos]*image.RGBA)
m.SchedRedraw()
2022-08-12 16:54:29 +00:00
}
2022-08-17 18:04:13 +00:00
// ChangeZoom adds to the zoom value and goes around to 32 once it hits 128
2022-08-12 16:54:29 +00:00
func (m *MapUI) ChangeZoom() {
2022-09-04 11:13:45 +00:00
m.zoomLevel /= 2
if m.zoomLevel == 0 {
m.zoomLevel = 16
}
2022-08-12 16:54:29 +00:00
m.SchedRedraw()
}
2022-08-17 18:04:13 +00:00
// SchedRedraw tells the map to redraw the next time its sent
2022-08-12 16:54:29 +00:00
func (m *MapUI) SchedRedraw() {
m.needRedraw = true
}
2022-08-18 16:18:16 +00:00
// draw_img_scaled_pos draws src onto dst at bottom_left, scaled to size
func draw_img_scaled_pos(dst *image.RGBA, src *image.RGBA, bottom_left image.Point, size_scaled int) {
sbx := src.Bounds().Dx()
2022-09-07 12:07:33 +00:00
ratio := int(float64(sbx) / float64(size_scaled))
2022-08-18 16:18:16 +00:00
2022-09-07 12:07:33 +00:00
for x_out := bottom_left.X; x_out < bottom_left.X+size_scaled; x_out++ {
for y_out := bottom_left.Y; y_out < bottom_left.Y+size_scaled; y_out++ {
x_in := (x_out - bottom_left.X) * ratio
y_in := (y_out - bottom_left.Y) * ratio
2022-08-18 16:18:16 +00:00
c := src.At(x_in, y_in)
dst.Set(x_out, y_out, c)
}
}
}
2022-08-11 20:22:39 +00:00
// draw chunk images to the map image
2022-09-04 11:13:45 +00:00
func (m *MapUI) Redraw() {
2022-09-05 15:40:03 +00:00
for {
2022-09-05 19:08:57 +00:00
r, ok := m.renderQueue.Dequeue().(*RenderElem)
if !ok {
2022-09-05 15:40:03 +00:00
break
}
if r.ch != nil {
2022-09-05 19:08:57 +00:00
m.renderedChunks[r.pos] = Chunk2Img(r.ch)
2022-09-05 15:40:03 +00:00
} else {
2022-09-05 19:08:57 +00:00
m.renderedChunks[r.pos] = black_16x16
2022-09-05 15:40:03 +00:00
}
}
2022-08-11 20:22:39 +00:00
// get the chunk coord bounds
2022-07-29 16:12:06 +00:00
min := protocol.ChunkPos{}
max := protocol.ChunkPos{}
2022-09-05 19:08:57 +00:00
for _ch := range m.renderedChunks {
2022-07-29 16:12:06 +00:00
if _ch.X() < min.X() {
min[0] = _ch.X()
}
if _ch.Z() < min.Z() {
min[1] = _ch.Z()
}
if _ch.X() > max.X() {
max[0] = _ch.X()
}
if _ch.Z() > max.Z() {
max[1] = _ch.Z()
}
}
2022-08-18 13:08:44 +00:00
middle := protocol.ChunkPos{
2022-09-04 11:13:45 +00:00
int32(m.w.PlayerPos.Position.X()),
int32(m.w.PlayerPos.Position.Z()),
2022-08-18 13:08:44 +00:00
}
2022-08-18 16:18:16 +00:00
chunks_x := int(max[0] - min[0] + 1) // how many chunk lengths is x coordinate
2022-08-20 12:13:56 +00:00
chunks_y := int(max[1] - min[1] + 1)
2022-09-04 11:13:45 +00:00
2022-09-07 12:07:33 +00:00
// total_width := 32 * math.Ceil(float64(chunks_x)/32)
chunks_per_line := float64(128 / m.zoomLevel)
px_per_block := 128 / chunks_per_line / 16 // how many pixels per block
sz_chunk := int(math.Floor(px_per_block * 16))
2022-07-29 16:12:06 +00:00
2022-08-12 16:54:29 +00:00
for i := 0; i < len(m.img.Pix); i++ { // clear canvas
m.img.Pix[i] = 0
2022-08-11 20:22:39 +00:00
}
2022-07-29 16:12:06 +00:00
2022-09-05 19:08:57 +00:00
for _ch := range m.renderedChunks {
2022-08-18 13:08:44 +00:00
relative_middle_x := float64(_ch.X()*16 - middle.X())
relative_middle_z := float64(_ch.Z()*16 - middle.Z())
2022-08-17 18:04:13 +00:00
px_pos := image.Point{ // bottom left corner of the chunk on the map
2022-08-18 13:08:44 +00:00
X: int(math.Floor(relative_middle_x*px_per_block)) + 64,
Y: int(math.Floor(relative_middle_z*px_per_block)) + 64,
2022-08-12 02:53:43 +00:00
}
if !m.img.Rect.Intersect(image.Rect(px_pos.X, px_pos.Y, px_pos.X+sz_chunk, px_pos.Y+sz_chunk)).Empty() {
2022-09-05 19:08:57 +00:00
draw_img_scaled_pos(m.img, m.renderedChunks[_ch], image.Point{
2022-08-18 16:18:16 +00:00
px_pos.X, px_pos.Y,
}, sz_chunk)
2022-08-12 02:53:43 +00:00
}
}
2022-08-18 16:18:16 +00:00
2022-08-20 12:13:56 +00:00
draw_full := false
if draw_full {
img2 := image.NewRGBA(image.Rect(0, 0, chunks_x*16, chunks_y*16))
middle_block_x := chunks_x / 2 * 16
middle_block_y := chunks_y / 2 * 16
2022-09-05 19:08:57 +00:00
for pos := range m.renderedChunks {
2022-08-20 12:13:56 +00:00
px_pos := image.Point{
2022-09-05 19:08:57 +00:00
X: int(pos.X()*16) - middle_block_x + img2.Rect.Dx(),
Y: int(pos.Z()*16) - middle_block_y + img2.Rect.Dy(),
2022-08-20 12:13:56 +00:00
}
draw.Draw(img2, image.Rect(
px_pos.X,
px_pos.Y,
px_pos.X+16,
px_pos.Y+16,
2022-09-05 19:08:57 +00:00
), m.renderedChunks[pos], image.Point{}, draw.Src)
2022-08-17 18:04:13 +00:00
}
2022-08-20 12:13:56 +00:00
buf := bytes.NewBuffer(nil)
bmp.Encode(buf, img2)
os.WriteFile("test.bmp", buf.Bytes(), 0o777)
2022-08-20 12:13:56 +00:00
}
2022-07-29 16:12:06 +00:00
}
2022-08-12 16:54:29 +00:00
func (m *MapUI) SetChunk(pos protocol.ChunkPos, ch *chunk.Chunk) {
2022-09-05 19:08:57 +00:00
m.renderQueue.Enqueue(&RenderElem{pos, ch})
2022-08-12 16:54:29 +00:00
m.SchedRedraw()
}