package main import ( "context" "errors" "flag" "fmt" "image" "image/draw" "log" "net" "os" "path" "time" "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/df-mc/goleveldb/leveldb/opt" "github.com/go-gl/mathgl/mgl32" "github.com/sandertv/gophertunnel/minecraft" "github.com/sandertv/gophertunnel/minecraft/protocol" "github.com/sandertv/gophertunnel/minecraft/protocol/packet" _ "github.com/df-mc/dragonfly/server/block" // to load blocks ) type TPlayerPos struct { Position mgl32.Vec3 Pitch float32 Yaw float32 HeadYaw float32 } // the state used for drawing and saving type WorldState struct { Provider *mcdb.Provider // provider for the current world chunks map[protocol.ChunkPos]*chunk.Chunk Dim world.Dimension WorldName string PlayerPos TPlayerPos ClientConn *minecraft.Conn ServerConn *minecraft.Conn // ui ui MapUI } func NewWorldState() *WorldState { return &WorldState{ Provider: nil, chunks: make(map[protocol.ChunkPos]*chunk.Chunk), Dim: nil, WorldName: "world", PlayerPos: TPlayerPos{}, ui: NewMapUI(), } } var black_16x16 = image.NewRGBA(image.Rect(0, 0, 16, 16)) func init() { draw.Draw(black_16x16, image.Rect(0, 0, 16, 16), image.Black, image.Point{}, draw.Src) register_command("world", "Launch world downloading proxy", world_main) } func world_main(ctx context.Context, args []string) error { var server string if len(args) >= 1 { server = args[0] args = args[1:] } _, server = server_input(server) flag.CommandLine.Parse(args) if G_help { flag.Usage() return nil } _status := minecraft.NewStatusProvider("Server") listener, err := minecraft.ListenConfig{ StatusProvider: _status, }.Listen("raknet", ":19132") if err != nil { return err } defer listener.Close() fmt.Printf("Listening on %s\n", listener.Addr()) c, err := listener.Accept() if err != nil { log.Fatal(err) } // not a goroutine, only 1 client at a time world_state := NewWorldState() world_state.handleConn(ctx, c.(*minecraft.Conn), listener, server) return nil } var dimension_ids = map[int32]world.Dimension{ 0: world.Overworld, 1: world.Nether, 2: world.End, } func (w *WorldState) ProcessLevelChunk(pk *packet.LevelChunk) { ch, err := chunk.NetworkDecode(uint32(pk.HighestSubChunk), pk.RawPayload, int(pk.SubChunkCount), w.Dim.Range()) if err != nil { log.Print(err.Error()) return } existing := w.chunks[pk.Position] if existing == nil { w.chunks[pk.Position] = ch w.ui.SetChunk(pk.Position, nil) } if pk.SubChunkRequestMode == protocol.SubChunkRequestModeLegacy { w.ui.SetChunk(pk.Position, ch) } else { // request all the subchunks var Offset_table = [][3]int8{ {0, 0, 0}, {0, 1, 0}, {0, 2, 0}, {0, 3, 0}, {0, 4, 0}, {0, 5, 0}, {0, 6, 0}, {0, 7, 0}, {0, 8, 0}, {0, 9, 0}, {0, 10, 0}, {0, 11, 0}, {0, 12, 0}, {0, 13, 0}, {0, 14, 0}, {0, 15, 0}, {0, 16, 0}, {0, 17, 0}, {0, 18, 0}, {0, 19, 0}, {0, 20, 0}, {0, 21, 0}, {0, 22, 0}, {0, 23, 0}, } max := len(Offset_table) - 1 if pk.SubChunkRequestMode == protocol.SubChunkRequestModeLimited { max = int(pk.HighestSubChunk) } w.ServerConn.WritePacket(&packet.SubChunkRequest{ Dimension: int32(w.Dim.EncodeDimension()), Position: protocol.SubChunkPos{ pk.Position.X(), 0, pk.Position.Z(), }, Offsets: Offset_table[:max], }) } } func (w *WorldState) ProcessSubChunk(pk *packet.SubChunk) { pos_to_redraw := make(map[protocol.ChunkPos]bool) for _, sub := range pk.SubChunkEntries { abs_x := pk.Position[0] + int32(sub.Offset[0]) abs_y := pk.Position[1] + int32(sub.Offset[1]) abs_z := pk.Position[2] + int32(sub.Offset[2]) pos := protocol.ChunkPos{abs_x, abs_z} ch := w.chunks[pos] if ch == nil { fmt.Printf("the server didnt send the chunk before the subchunk!\n") continue } err := ch.ApplySubChunkEntry(int(abs_y), &sub) if err != nil { fmt.Print(err) } pos_to_redraw[pos] = true } // redraw the chunks for pos := range pos_to_redraw { w.ui.SetChunk(pos, w.chunks[pos]) } w.ui.SchedRedraw() } func (w *WorldState) ProcessAnimate(pk *packet.Animate) { if pk.ActionType == packet.AnimateActionSwingArm { w.ui.ChangeZoom() send_popup(w.ClientConn, fmt.Sprintf("Zoom: %d", w.ui.zoom)) w.ui.Send(w) } } func (w *WorldState) ProcessChangeDimension(pk *packet.ChangeDimension) { fmt.Printf("ChangeDimension %d\n", pk.Dimension) folder := path.Join("worlds", fmt.Sprintf("%s-dim-%d", w.WorldName, pk.Dimension)) provider, err := mcdb.New(folder, opt.DefaultCompression) if err != nil { log.Fatal(err) } if w.Provider != nil { w.Provider.Close() } w.Provider = provider w.Dim = dimension_ids[pk.Dimension] w.chunks = make(map[protocol.ChunkPos]*chunk.Chunk) w.ui.Reset() } func (w *WorldState) SetPlayerPos(Position mgl32.Vec3, Pitch, Yaw, HeadYaw float32) { w.PlayerPos = TPlayerPos{ Position: Position, Pitch: Pitch, Yaw: Yaw, HeadYaw: HeadYaw, } } func (w *WorldState) handleConn(ctx context.Context, conn *minecraft.Conn, listener *minecraft.Listener, target string) { w.ClientConn = conn var packet_func func(header packet.Header, payload []byte, src, dst net.Addr) = nil if G_debug { packet_func = PacketLogger } fmt.Printf("Connecting to %s\n", target) serverConn, err := minecraft.Dialer{ TokenSource: G_src, ClientData: conn.ClientData(), PacketFunc: packet_func, }.DialContext(ctx, "raknet", target) if err != nil { fmt.Fprintf(os.Stderr, "Failed to connect to %s: %s\n", target, err) return } w.ServerConn = serverConn if err := spawn_conn(ctx, conn, serverConn); err != nil { fmt.Fprintf(os.Stderr, "Failed to spawn: %s\n", err) return } G_exit = append(G_exit, func() { serverConn.Close() conn.WritePacket(&packet.Disconnect{ Message: "Closing", HideDisconnectionScreen: false, }) conn.Close() listener.Close() println("Closing Provider") w.Provider.Close() }) done := make(chan struct{}) go func() { // client loop defer listener.Disconnect(conn, "connection lost") defer serverConn.Close() defer func() { done <- struct{}{} }() for { skip := false pk, err := conn.ReadPacket() if err != nil { return } 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: if pk.MapID == VIEW_MAP_ID { w.ui.Send(w) skip = true } case *packet.ClientCacheStatus: pk.Enabled = false serverConn.WritePacket(pk) skip = true case *packet.Animate: w.ProcessAnimate(pk) } if !skip { if err := serverConn.WritePacket(pk); err != nil { if disconnect, ok := errors.Unwrap(err).(minecraft.DisconnectError); ok { _ = listener.Disconnect(conn, disconnect.Error()) } return } } } }() go func() { // send map item select { case <-ctx.Done(): return default: for { time.Sleep(1 * time.Second) err := conn.WritePacket(&MAP_ITEM_PACKET) if err != nil { return } } } }() go func() { // server loop defer serverConn.Close() defer listener.Disconnect(conn, "connection lost") defer func() { done <- struct{}{} }() gd := serverConn.GameData() w.ProcessChangeDimension(&packet.ChangeDimension{ Dimension: gd.Dimension, Position: gd.PlayerPosition, Respawn: false, }) 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: w.ProcessChangeDimension(pk) case *packet.LevelChunk: w.ProcessLevelChunk(pk) w.ui.Send(w) send_popup(conn, fmt.Sprintf("%d chunks loaded\n", len(w.chunks))) case *packet.SubChunk: w.ProcessSubChunk(pk) } if err := conn.WritePacket(pk); err != nil { return } } }() select { case <-ctx.Done(): return case <-done: return } }