bedrocktool/handlers/worlds/map_item.go

308 lines
7.2 KiB
Go
Raw Normal View History

2023-04-04 00:44:13 +00:00
package worlds
2022-07-29 16:12:06 +00:00
import (
"image"
2022-08-20 12:13:56 +00:00
"image/draw"
2022-08-12 02:53:43 +00:00
"math"
2023-03-08 11:46:16 +00:00
"sync"
2022-09-04 11:13:45 +00:00
"time"
2022-07-29 16:12:06 +00:00
2023-02-12 21:22:44 +00:00
"github.com/bedrock-tool/bedrocktool/locale"
2023-03-18 11:12:54 +00:00
"github.com/bedrock-tool/bedrocktool/ui/messages"
2022-09-04 14:53:21 +00:00
"github.com/bedrock-tool/bedrocktool/utils"
2023-04-04 00:42:17 +00:00
"github.com/go-gl/mathgl/mgl32"
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-07-29 16:12:06 +00:00
)
2023-01-29 21:20:13 +00:00
const ViewMapID = 0x424242
2022-07-29 16:12:06 +00:00
2023-01-29 21:20:13 +00:00
// MapItemPacket tells the client that it has a map with id 0x424242 in the offhand
2023-04-12 12:10:45 +00:00
var MapItemPacket = packet.InventoryContent{
2022-07-29 16:12:06 +00:00
WindowID: 119,
Content: []protocol.ItemInstance{
{
2023-01-24 21:41:37 +00:00
StackNetworkID: 1, // random if auth inv
2022-07-29 16:12:06 +00:00
Stack: protocol.ItemStack{
ItemType: protocol.ItemType{
2023-01-24 21:41:37 +00:00
NetworkID: 420, // overwritten in onconnect
2022-07-29 16:12:06 +00:00
MetadataValue: 0,
},
BlockRuntimeID: 0,
Count: 1,
NBTData: map[string]interface{}{
2023-02-05 16:41:06 +00:00
"map_name_index": int64(1),
"map_uuid": int64(ViewMapID),
2022-07-29 16:12:06 +00:00
},
},
},
},
}
2023-04-10 17:55:08 +00:00
func imin(a, b int32) int32 {
if a < b {
return a
}
return b
}
func imax(a, b int32) int32 {
if a > b {
return a
}
return b
}
func (m *MapUI) GetBounds() (min, max protocol.ChunkPos) {
2023-04-10 17:55:08 +00:00
if len(m.renderedChunks) == 0 {
return
}
min = protocol.ChunkPos{math.MaxInt32, math.MaxInt32}
for chunk := range m.renderedChunks {
min[0] = imin(min[0], chunk[0])
min[1] = imin(min[1], chunk[1])
max[0] = imax(max[0], chunk[0])
max[1] = imax(max[1], chunk[1])
2022-09-09 12:58:02 +00:00
}
return
}
2022-09-05 15:40:03 +00:00
type RenderElem struct {
2023-04-10 17:55:08 +00:00
pos protocol.ChunkPos
ch *chunk.Chunk
2022-09-05 15:40:03 +00:00
}
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
showOnGui bool
2023-03-08 11:46:16 +00:00
l sync.RWMutex
2022-09-04 11:13:45 +00:00
ticker *time.Ticker
2023-04-04 00:42:17 +00:00
w *worldsHandler
2022-08-12 16:54:29 +00:00
}
2023-04-04 00:42:17 +00:00
func NewMapUI(w *worldsHandler) *MapUI {
2022-09-05 15:40:03 +00:00
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() {
2023-04-01 22:22:50 +00:00
r := m.w.gui.Message(messages.CanShowImages{})
if r.Ok {
m.showOnGui = true
}
2023-02-05 16:41:06 +00:00
// init map
2023-03-23 19:39:47 +00:00
err := m.w.proxy.ClientWritePacket(&packet.ClientBoundMapItemData{
MapID: ViewMapID,
Scale: 4,
MapsIncludedIn: []int64{ViewMapID},
UpdateFlags: packet.MapUpdateFlagInitialisation,
})
if err != nil {
logrus.Error(err)
return
2023-02-05 16:41:06 +00:00
}
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()
2023-03-23 19:39:47 +00:00
if err := m.w.proxy.ClientWritePacket(&packet.ClientBoundMapItemData{
MapID: ViewMapID,
Scale: 4,
Width: 128,
Height: 128,
Pixels: utils.Img2rgba(m.img),
UpdateFlags: packet.MapUpdateFlagTexture,
}); err != nil {
logrus.Error(err)
return
2022-09-04 11:13:45 +00:00
}
}
}
}()
go func() { // send map item
t := time.NewTicker(1 * time.Second)
for range t.C {
if m.w.ctx.Err() != nil {
return
}
2023-03-23 19:39:47 +00:00
err := m.w.proxy.ClientWritePacket(&MapItemPacket)
if err != nil {
logrus.Error(err)
return
}
}
}()
2022-09-04 11:13:45 +00:00
}
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() {
2023-03-08 11:46:16 +00:00
m.l.Lock()
2022-09-05 19:08:57 +00:00
m.renderedChunks = make(map[protocol.ChunkPos]*image.RGBA)
2023-03-08 11:46:16 +00:00
m.l.Unlock()
2022-09-05 19:08:57 +00:00
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
}
2023-01-29 21:20:13 +00:00
// Redraw draws chunk images to the map image
2022-09-04 11:13:45 +00:00
func (m *MapUI) Redraw() {
2023-03-08 11:46:16 +00:00
m.l.Lock()
2023-04-01 22:22:50 +00:00
updatedChunks := make([]protocol.ChunkPos, 0, m.renderQueue.Length())
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 {
2023-04-10 17:55:08 +00:00
m.renderedChunks[r.pos] = utils.Chunk2Img(r.ch)
2022-09-05 15:40:03 +00:00
}
2023-03-14 01:07:39 +00:00
updatedChunks = append(updatedChunks, r.pos)
2022-09-05 15:40:03 +00:00
}
2023-03-08 11:46:16 +00:00
m.l.Unlock()
2022-09-05 15:40:03 +00:00
2022-08-18 13:08:44 +00:00
middle := protocol.ChunkPos{
2023-03-30 11:48:03 +00:00
int32(m.w.serverState.PlayerPos.Position.X()),
int32(m.w.serverState.PlayerPos.Position.Z()),
2022-08-18 13:08:44 +00:00
}
2023-01-29 21:20:13 +00:00
chunksPerLine := float64(128 / m.zoomLevel)
pxPerBlock := 128 / chunksPerLine / 16 // how many pixels per block
pxSizeChunk := int(math.Floor(pxPerBlock * 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
2023-03-08 11:46:16 +00:00
m.l.RLock()
2022-09-05 19:08:57 +00:00
for _ch := range m.renderedChunks {
2023-01-29 21:20:13 +00:00
relativeMiddleX := float64(_ch.X()*16 - middle.X())
relativeMiddleZ := float64(_ch.Z()*16 - middle.Z())
px := image.Point{ // bottom left corner of the chunk on the map
X: int(math.Floor(relativeMiddleX*pxPerBlock)) + 64,
Y: int(math.Floor(relativeMiddleZ*pxPerBlock)) + 64,
2022-08-12 02:53:43 +00:00
}
2023-01-29 21:20:13 +00:00
if !m.img.Rect.Intersect(image.Rect(px.X, px.Y, px.X+pxSizeChunk, px.Y+pxSizeChunk)).Empty() {
utils.DrawImgScaledPos(m.img, m.renderedChunks[_ch], px, pxSizeChunk)
2022-08-12 02:53:43 +00:00
}
}
if m.showOnGui {
2023-03-14 01:07:39 +00:00
min, max := m.GetBounds()
2023-04-01 22:22:50 +00:00
m.w.gui.Message(messages.UpdateMap{
2023-03-25 21:19:14 +00:00
ChunkCount: len(m.renderedChunks),
2023-03-30 11:48:03 +00:00
Rotation: m.w.serverState.PlayerPos.Yaw,
2023-03-14 01:07:39 +00:00
UpdatedTiles: updatedChunks,
Tiles: m.renderedChunks,
BoundsMin: min,
BoundsMax: max,
2023-03-08 11:46:16 +00:00
})
}
2023-03-14 01:07:39 +00:00
m.l.RUnlock()
2022-07-29 16:12:06 +00:00
}
2022-09-09 12:58:02 +00:00
func (m *MapUI) ToImage() *image.RGBA {
// get the chunk coord bounds
min, max := m.GetBounds()
2023-01-29 21:20:13 +00:00
chunksX := int(max[0] - min[0] + 1) // how many chunk lengths is x coordinate
chunksY := int(max[1] - min[1] + 1)
2022-09-09 12:58:02 +00:00
2023-04-12 12:10:45 +00:00
img := image.NewRGBA(image.Rect(0, 0, chunksX*16, chunksY*16))
2022-09-09 12:58:02 +00:00
2023-03-08 11:46:16 +00:00
m.l.RLock()
for pos, tile := range m.renderedChunks {
2023-03-05 19:08:30 +00:00
px := image.Pt(
int((pos.X()-min.X())*16),
int((pos.Z()-min.Z())*16),
)
2023-04-12 12:10:45 +00:00
draw.Draw(img, image.Rect(
2023-03-05 19:08:30 +00:00
px.X, px.Y,
px.X+16, px.Y+16,
2023-03-08 11:46:16 +00:00
), tile, image.Point{}, draw.Src)
2022-09-09 12:58:02 +00:00
}
2023-03-08 11:46:16 +00:00
m.l.RUnlock()
2023-04-12 12:10:45 +00:00
return img
2022-09-09 12:58:02 +00:00
}
2023-03-25 21:19:14 +00:00
func (m *MapUI) SetChunk(pos protocol.ChunkPos, ch *chunk.Chunk, complete bool) {
2023-04-10 17:55:08 +00:00
m.renderQueue.Enqueue(&RenderElem{pos, ch})
2022-08-12 16:54:29 +00:00
m.SchedRedraw()
}
2023-04-04 00:42:17 +00:00
func (w *worldsHandler) ProcessAnimate(pk *packet.Animate) {
2023-02-12 21:22:44 +00:00
if pk.ActionType == packet.AnimateActionSwingArm {
w.mapUI.ChangeZoom()
w.proxy.SendPopup(locale.Loc("zoom_level", locale.Strmap{"Level": w.mapUI.zoomLevel}))
2023-02-12 21:22:44 +00:00
}
}
2023-04-04 00:42:17 +00:00
func (w *worldsHandler) SetPlayerPos(Position mgl32.Vec3, Pitch, Yaw, HeadYaw float32) {
last := w.serverState.PlayerPos
current := TPlayerPos{
Position: Position,
Pitch: Pitch,
Yaw: Yaw,
HeadYaw: HeadYaw,
}
w.serverState.PlayerPos = current
if int(last.Position.X()) != int(current.Position.X()) || int(last.Position.Z()) != int(current.Position.Z()) {
w.mapUI.SchedRedraw()
}
}
func (w *worldsHandler) processMapPacketsClient(pk packet.Packet, forward *bool) packet.Packet {
switch pk := pk.(type) {
case *packet.MovePlayer:
w.SetPlayerPos(pk.Position, pk.Pitch, pk.Yaw, pk.HeadYaw)
case *packet.PlayerAuthInput:
w.SetPlayerPos(pk.Position, pk.Pitch, pk.Yaw, pk.HeadYaw)
case *packet.MapInfoRequest:
2023-01-29 21:20:13 +00:00
if pk.MapID == ViewMapID {
w.mapUI.SchedRedraw()
*forward = false
}
case *packet.Animate:
w.ProcessAnimate(pk)
}
return pk
}