mirror of
https://github.com/CosmicStar98/bedrocktool.git
synced 2024-06-16 21:49:45 +00:00
347 lines
8.0 KiB
Go
347 lines
8.0 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"log"
|
|
"math"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
|
|
"gioui.org/app"
|
|
"gioui.org/f32"
|
|
"gioui.org/font/gofont"
|
|
"gioui.org/io/system"
|
|
"gioui.org/layout"
|
|
"gioui.org/op"
|
|
"gioui.org/op/clip"
|
|
"gioui.org/op/paint"
|
|
"gioui.org/text"
|
|
"gioui.org/widget"
|
|
"gioui.org/widget/material"
|
|
"github.com/df-mc/dragonfly/server/block/cube"
|
|
"github.com/df-mc/dragonfly/server/world"
|
|
"github.com/df-mc/dragonfly/server/world/chunk"
|
|
"github.com/df-mc/dragonfly/server/world/mcdb"
|
|
"github.com/sandertv/gophertunnel/minecraft"
|
|
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
|
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
|
)
|
|
|
|
const (
|
|
state_not_connected = iota
|
|
state_working
|
|
state_saving
|
|
state_done
|
|
)
|
|
|
|
var G_window *app.Window
|
|
var G_state int = state_working
|
|
var theme = material.NewTheme(gofont.Collection())
|
|
var finish_button widget.Clickable
|
|
|
|
// the state used for drawing and saving
|
|
type WorldState struct {
|
|
Dimension int
|
|
Dimensions map[int]*mcdb.Provider
|
|
Entities map[int][]world.SaveableEntity
|
|
ChunkCount int
|
|
PlayerPos packet.MovePlayer
|
|
img image.NRGBA
|
|
_mutex sync.Mutex
|
|
}
|
|
|
|
var world_state *WorldState = &WorldState{
|
|
Dimensions: make(map[int]*mcdb.Provider),
|
|
Entities: make(map[int][]world.SaveableEntity),
|
|
PlayerPos: packet.MovePlayer{},
|
|
_mutex: sync.Mutex{},
|
|
}
|
|
|
|
func init() {
|
|
register_command("world", "Launch world downloading proxy", world_main)
|
|
}
|
|
|
|
func world_main(args []string) error {
|
|
var target string
|
|
var help bool
|
|
flag.StringVar(&target, "target", "", "target server")
|
|
flag.BoolVar(&help, "help", false, "show help")
|
|
fmt.Printf("%v\n", args)
|
|
flag.CommandLine.Parse(args)
|
|
if help {
|
|
flag.Usage()
|
|
return nil
|
|
}
|
|
|
|
if target == "" {
|
|
target = input_server()
|
|
}
|
|
if len(strings.Split(target, ":")) == 1 {
|
|
target += ":19132"
|
|
}
|
|
|
|
_status := minecraft.NewStatusProvider("Server")
|
|
listener, err := minecraft.ListenConfig{
|
|
StatusProvider: _status,
|
|
}.Listen("raknet", ":19132")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer listener.Close()
|
|
|
|
go func() {
|
|
for {
|
|
c, err := listener.Accept()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// not a goroutine, only 1 client at a time
|
|
handleConn(c.(*minecraft.Conn), listener, target)
|
|
}
|
|
}()
|
|
|
|
go func() {
|
|
G_window = app.NewWindow()
|
|
if err := run_gui(target); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
os.Exit(0)
|
|
}()
|
|
app.Main()
|
|
return nil
|
|
}
|
|
|
|
func ProcessSubChunk(sub_chunk *packet.SubChunk) {
|
|
}
|
|
|
|
func draw_chunk(pos protocol.ChunkPos, ch *chunk.Chunk) {
|
|
// TODO
|
|
}
|
|
|
|
func ProcessChunk(pk *packet.LevelChunk) {
|
|
ch, err := chunk.NetworkDecode(uint32(pk.HighestSubChunk), pk.RawPayload, int(pk.SubChunkCount), cube.Range{-64, 320})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
world_state.Dimensions[world_state.Dimension].SaveChunk(world.ChunkPos(pk.Position), ch)
|
|
world_state.ChunkCount++
|
|
draw_chunk(pk.Position, ch)
|
|
G_window.Invalidate()
|
|
}
|
|
|
|
func ProcessActor(actor *packet.AddActor) {
|
|
// TODO
|
|
}
|
|
|
|
func ProcessBlockUpdate(update *packet.UpdateBlock) {
|
|
// TODO
|
|
}
|
|
|
|
func ProcessMove(player *packet.MovePlayer) {
|
|
world_state.PlayerPos = *player
|
|
G_window.Invalidate()
|
|
}
|
|
|
|
func SetState(state int) {
|
|
G_state = state
|
|
G_window.Invalidate()
|
|
}
|
|
|
|
func handleConn(conn *minecraft.Conn, listener *minecraft.Listener, target string) {
|
|
var packet_func func(header packet.Header, payload []byte, src, dst net.Addr) = nil
|
|
if G_debug {
|
|
packet_func = PacketLogger
|
|
}
|
|
|
|
serverConn, err := minecraft.Dialer{
|
|
TokenSource: G_src,
|
|
ClientData: conn.ClientData(),
|
|
PacketFunc: packet_func,
|
|
}.Dial("raknet", target)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
var g sync.WaitGroup
|
|
g.Add(2)
|
|
go func() {
|
|
if err := conn.StartGame(serverConn.GameData()); err != nil {
|
|
panic(err)
|
|
}
|
|
g.Done()
|
|
}()
|
|
go func() {
|
|
if err := serverConn.DoSpawn(); err != nil {
|
|
panic(err)
|
|
}
|
|
g.Done()
|
|
}()
|
|
g.Wait()
|
|
|
|
SetState(state_working)
|
|
|
|
go func() { // client loop
|
|
defer listener.Disconnect(conn, "connection lost")
|
|
defer serverConn.Close()
|
|
defer finish_button.Click()
|
|
for {
|
|
pk, err := conn.ReadPacket()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
switch _pk := pk.(type) {
|
|
case *packet.RequestChunkRadius:
|
|
pk = &packet.RequestChunkRadius{ // rewrite packet to send a bigger radius
|
|
ChunkRadius: 32,
|
|
}
|
|
case *packet.MovePlayer:
|
|
ProcessMove(_pk)
|
|
}
|
|
|
|
if err := serverConn.WritePacket(pk); err != nil {
|
|
if disconnect, ok := errors.Unwrap(err).(minecraft.DisconnectError); ok {
|
|
_ = listener.Disconnect(conn, disconnect.Error())
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
go func() { // server loop
|
|
defer serverConn.Close()
|
|
defer listener.Disconnect(conn, "connection lost")
|
|
defer finish_button.Click()
|
|
for {
|
|
pk, err := serverConn.ReadPacket()
|
|
if err != nil {
|
|
if disconnect, ok := errors.Unwrap(err).(minecraft.DisconnectError); ok {
|
|
_ = listener.Disconnect(conn, disconnect.Error())
|
|
}
|
|
return
|
|
}
|
|
|
|
switch pk := pk.(type) {
|
|
case *packet.ChangeDimension:
|
|
world_state.Dimension = int(pk.Dimension)
|
|
world_state.Entities[world_state.Dimension] = nil
|
|
world_state.Dimensions[world_state.Dimension], err = mcdb.New(fmt.Sprintf("worlds/%d", world_state.Dimension), world.Overworld)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
case *packet.LevelChunk:
|
|
ProcessChunk(pk)
|
|
case *packet.SubChunk:
|
|
ProcessSubChunk(pk)
|
|
case *packet.AddActor:
|
|
ProcessActor(pk)
|
|
case *packet.UpdateBlock:
|
|
ProcessBlockUpdate(pk)
|
|
case *packet.ChunkRadiusUpdated:
|
|
fmt.Printf("ChunkRadiusUpdated: %d\n", pk.ChunkRadius)
|
|
}
|
|
|
|
if err := conn.WritePacket(pk); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func draw_rect(gtx layout.Context, rect image.Rectangle, col color.NRGBA) {
|
|
cl := clip.Rect{Min: rect.Min, Max: rect.Max}.Push(gtx.Ops)
|
|
paint.ColorOp{Color: col}.Add(gtx.Ops)
|
|
paint.PaintOp{}.Add(gtx.Ops)
|
|
cl.Pop()
|
|
}
|
|
|
|
func layout_chunks(gtx layout.Context) layout.Dimensions {
|
|
world_state._mutex.Lock()
|
|
draw_player_icon(gtx)
|
|
world_state._mutex.Unlock()
|
|
return layout.Dimensions{Size: image.Point{X: 100, Y: 100}}
|
|
}
|
|
|
|
func draw_player_icon(gtx layout.Context) {
|
|
player := world_state.PlayerPos
|
|
|
|
op.Affine(f32.Affine2D{}.Rotate(f32.Pt(5, 5), player.HeadYaw*(math.Pi/180))).Add(gtx.Ops) // rotate and offset relative to first chunk
|
|
draw_rect(gtx, image.Rectangle{image.Point{X: 0, Y: 0}, image.Point{X: 10, Y: 10}}, color.NRGBA{255, 180, 0, 255})
|
|
}
|
|
|
|
func draw_working(gtx layout.Context) {
|
|
layout.Stack{
|
|
Alignment: layout.Center,
|
|
}.Layout(gtx,
|
|
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
|
return layout.Flex{
|
|
Axis: layout.Vertical,
|
|
}.Layout(gtx,
|
|
layout.Flexed(0.1, func(gtx layout.Context) layout.Dimensions { // top text
|
|
title := material.H2(theme, fmt.Sprintf("Chunks: %d\n", world_state.ChunkCount))
|
|
title.Alignment = text.Middle
|
|
title.Color = color.NRGBA{R: 127, G: 0, B: 0, A: 255}
|
|
return title.Layout(gtx)
|
|
}),
|
|
layout.Flexed(0.9, func(gtx layout.Context) layout.Dimensions { // centered chunk view
|
|
return layout.Center.Layout(gtx, layout_chunks)
|
|
}),
|
|
)
|
|
}),
|
|
layout.Stacked(func(gtx layout.Context) layout.Dimensions {
|
|
b := material.Button(theme, &finish_button, "Finish")
|
|
b.Color = color.NRGBA{R: 255, G: 255, B: 255, A: 255}
|
|
return b.Layout(gtx)
|
|
}),
|
|
)
|
|
|
|
if finish_button.Clicked() {
|
|
SetState(state_saving)
|
|
go begin_save_world(world_state)
|
|
}
|
|
}
|
|
|
|
func begin_save_world(world *WorldState) {
|
|
for i, dim := range world.Dimensions {
|
|
dim.Close()
|
|
world.Dimensions[i] = nil
|
|
}
|
|
}
|
|
|
|
func draw_saving(gtx layout.Context) {
|
|
|
|
}
|
|
|
|
func run_gui(target string) error {
|
|
th := material.NewTheme(gofont.Collection())
|
|
var ops op.Ops
|
|
|
|
for {
|
|
e := <-G_window.Events()
|
|
switch e := e.(type) {
|
|
case system.DestroyEvent:
|
|
return e.Err
|
|
case system.FrameEvent:
|
|
gtx := layout.NewContext(&ops, e)
|
|
|
|
switch G_state {
|
|
case state_not_connected:
|
|
title := material.H1(th, fmt.Sprintf("Connect to %s to start", "thelocalserverip"))
|
|
title.Alignment = text.Middle
|
|
title.Color = color.NRGBA{R: 127, G: 0, B: 0, A: 255}
|
|
title.Layout(gtx)
|
|
case state_working:
|
|
draw_working(gtx)
|
|
case state_saving:
|
|
draw_saving(gtx)
|
|
}
|
|
|
|
e.Frame(gtx.Ops)
|
|
}
|
|
}
|
|
}
|