refactor, world initial saving, ui ingame
This commit is contained in:
parent
eebc48021a
commit
f93f2b794b
114
main.go
114
main.go
|
@ -3,14 +3,17 @@ package main
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/sandertv/gophertunnel/minecraft/auth"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
|
@ -22,6 +25,8 @@ const TOKEN_FILE = "token.json"
|
|||
|
||||
var G_src oauth2.TokenSource
|
||||
var G_debug bool
|
||||
var G_help bool
|
||||
var G_exit func() = func() { os.Exit(0) }
|
||||
|
||||
func get_token() oauth2.Token {
|
||||
var token oauth2.Token
|
||||
|
@ -49,6 +54,25 @@ func get_token() oauth2.Token {
|
|||
return token
|
||||
}
|
||||
|
||||
func server_input(ctx context.Context, server string) (string, string) {
|
||||
if server == "" {
|
||||
fmt.Printf("Enter Server: ")
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
server, _ = reader.ReadString('\n')
|
||||
r, _ := regexp.Compile(`[\n\r]`)
|
||||
server = string(r.ReplaceAll([]byte(server), []byte("")))
|
||||
}
|
||||
if len(strings.Split(server, ":")) == 1 {
|
||||
server += ":19132"
|
||||
}
|
||||
host, _, err := net.SplitHostPort(server)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Invalid server: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return host, server
|
||||
}
|
||||
|
||||
var pool = packet.NewPool()
|
||||
|
||||
func PacketLogger(header packet.Header, payload []byte, src, dst net.Addr) {
|
||||
|
@ -58,29 +82,55 @@ func PacketLogger(header packet.Header, payload []byte, src, dst net.Addr) {
|
|||
pkFunc, ok := pool[header.PacketID]
|
||||
if !ok {
|
||||
pk = &packet.Unknown{PacketID: header.PacketID}
|
||||
} else {
|
||||
pk = pkFunc()
|
||||
}
|
||||
pk = pkFunc()
|
||||
pk.Unmarshal(r)
|
||||
dir := "<-C"
|
||||
if strings.HasPrefix(strings.Split(src.String(), ":")[1], "19132") {
|
||||
dir = "S->"
|
||||
}
|
||||
fmt.Printf("P: %s 0x%x, %s\n", dir, pk.ID(), reflect.TypeOf(pk))
|
||||
switch p := pk.(type) {
|
||||
case *packet.ResourcePackDataInfo:
|
||||
fmt.Printf("info %s\n", p.UUID)
|
||||
switch pk.(type) {
|
||||
case *packet.UpdateBlock:
|
||||
return
|
||||
case *packet.MoveActorAbsolute:
|
||||
return
|
||||
case *packet.SetActorMotion:
|
||||
return
|
||||
case *packet.SetTime:
|
||||
return
|
||||
case *packet.RemoveActor:
|
||||
return
|
||||
case *packet.AddActor:
|
||||
return
|
||||
case *packet.UpdateAttributes:
|
||||
return
|
||||
case *packet.Interact:
|
||||
return
|
||||
case *packet.LevelEvent:
|
||||
return
|
||||
case *packet.SetActorData:
|
||||
return
|
||||
case *packet.MoveActorDelta:
|
||||
return
|
||||
case *packet.MovePlayer:
|
||||
return
|
||||
case *packet.BlockActorData:
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("P: %s 0x%x, %s\n", dir, pk.ID(), reflect.TypeOf(pk))
|
||||
}
|
||||
|
||||
type CMD struct {
|
||||
Name string
|
||||
Desc string
|
||||
Main func([]string) error
|
||||
Main func(context.Context, []string) error
|
||||
}
|
||||
|
||||
var cmds map[string]CMD = make(map[string]CMD)
|
||||
|
||||
func register_command(name, desc string, main_func func([]string) error) {
|
||||
func register_command(name, desc string, main_func func(context.Context, []string) error) {
|
||||
cmds[name] = CMD{
|
||||
Name: name,
|
||||
Desc: desc,
|
||||
|
@ -88,35 +138,43 @@ func register_command(name, desc string, main_func func([]string) error) {
|
|||
}
|
||||
}
|
||||
|
||||
func input_server() string {
|
||||
fmt.Printf("Enter Server: ")
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
target, _ := reader.ReadString('\n')
|
||||
r, _ := regexp.Compile(`[\n\r]`)
|
||||
target = string(r.ReplaceAll([]byte(target), []byte("")))
|
||||
return target
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.BoolVar(&G_debug, "debug", false, "debug mode")
|
||||
flag.BoolVar(&G_help, "help", false, "show help")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-sigs
|
||||
fmt.Printf("\nExiting\n")
|
||||
cancel()
|
||||
G_exit()
|
||||
}()
|
||||
|
||||
// authenticate
|
||||
token := get_token()
|
||||
G_src = auth.RefreshTokenSource(&token)
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("Available commands:")
|
||||
for name, cmd := range cmds {
|
||||
fmt.Printf("\t%s\t%s\n", name, cmd.Desc)
|
||||
}
|
||||
fmt.Printf("Use '%s <command>' to run a command\n", os.Args[0])
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
fmt.Println("Available commands:")
|
||||
for name, cmd := range cmds {
|
||||
fmt.Printf("\t%s\t%s\n", name, cmd.Desc)
|
||||
}
|
||||
fmt.Printf("Use '%s <command>' to run a command\n", os.Args[0])
|
||||
|
||||
fmt.Printf("Input Command: ")
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
target, _ := reader.ReadString('\n')
|
||||
r, _ := regexp.Compile(`[\n\r]`)
|
||||
target = string(r.ReplaceAll([]byte(target), []byte("")))
|
||||
os.Args = append(os.Args, target)
|
||||
fmt.Printf("Input Command: ")
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
target, _ := reader.ReadString('\n')
|
||||
r, _ := regexp.Compile(`[\n\r]`)
|
||||
target = string(r.ReplaceAll([]byte(target), []byte("")))
|
||||
os.Args = append(os.Args, target)
|
||||
}
|
||||
}
|
||||
|
||||
cmd := cmds[os.Args[1]]
|
||||
|
@ -124,7 +182,7 @@ func main() {
|
|||
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", os.Args[1])
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := cmd.Main(os.Args[2:]); err != nil {
|
||||
if err := cmd.Main(ctx, os.Args[2:]); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
241
skins.go
241
skins.go
|
@ -11,25 +11,102 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/sandertv/gophertunnel/minecraft"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol"
|
||||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
)
|
||||
|
||||
type Skin struct {
|
||||
protocol.Skin
|
||||
}
|
||||
|
||||
func (skin *Skin) WriteGeometry(output_path string) error {
|
||||
os.Mkdir(output_path, 0755)
|
||||
f, err := os.Create(path.Join(output_path, "geometry.json"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Geometry %s: %s", output_path, err)
|
||||
}
|
||||
defer f.Close()
|
||||
io.Copy(f, bytes.NewReader(skin.SkinGeometry))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (skin *Skin) WriteCape(output_path string) error {
|
||||
os.Mkdir(output_path, 0755)
|
||||
f, err := os.Create(path.Join(output_path, "cape.png"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write Cape %s: %s", output_path, err)
|
||||
}
|
||||
defer f.Close()
|
||||
cape_tex := image.NewRGBA(image.Rect(0, 0, int(skin.CapeImageWidth), int(skin.CapeImageHeight)))
|
||||
cape_tex.Pix = skin.CapeData
|
||||
|
||||
if err := png.Encode(f, cape_tex); err != nil {
|
||||
return fmt.Errorf("error writing skin: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (skin *Skin) WriteAnimations(output_path string) error {
|
||||
os.Mkdir(output_path, 0755)
|
||||
fmt.Printf("%s has animations (unimplemented)\n", output_path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (skin *Skin) WriteTexture(output_path string) error {
|
||||
f, err := os.Create(output_path + ".png")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing Texture: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
skin_tex := image.NewRGBA(image.Rect(0, 0, int(skin.SkinImageWidth), int(skin.SkinImageHeight)))
|
||||
skin_tex.Pix = skin.SkinData
|
||||
|
||||
if err := png.Encode(f, skin_tex); err != nil {
|
||||
return fmt.Errorf("error writing Texture: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (skin *Skin) Write(output_path, name string) error {
|
||||
complex := false
|
||||
skin_dir := path.Join(output_path, name)
|
||||
if len(skin.SkinGeometry) > 0 {
|
||||
if err := skin.WriteGeometry(skin_dir); err != nil {
|
||||
return err
|
||||
}
|
||||
complex = true
|
||||
}
|
||||
if len(skin.CapeData) > 0 {
|
||||
if err := skin.WriteCape(skin_dir); err != nil {
|
||||
return err
|
||||
}
|
||||
complex = true
|
||||
}
|
||||
if len(skin.Animations) > 0 {
|
||||
if err := skin.WriteAnimations(skin_dir); err != nil {
|
||||
return err
|
||||
}
|
||||
complex = true
|
||||
}
|
||||
|
||||
var err error
|
||||
if complex {
|
||||
err = skin.WriteTexture(path.Join(skin_dir, "skin"))
|
||||
} else {
|
||||
err = skin.WriteTexture(skin_dir)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
register_command("skins", "skin stealer", skin_main)
|
||||
}
|
||||
|
||||
var players = make(map[string]string)
|
||||
var player_counts = make(map[string]int)
|
||||
|
||||
var out_path string
|
||||
var name_regexp = regexp.MustCompile(`§.`)
|
||||
|
||||
func cleanup_name(name string) string {
|
||||
|
@ -46,124 +123,40 @@ func cleanup_name(name string) string {
|
|||
return name
|
||||
}
|
||||
|
||||
func write_skin_geometry(output_path string, skin protocol.Skin) {
|
||||
os.Mkdir(output_path, 0755)
|
||||
f, err := os.Create(path.Join(output_path, "geometry.json"))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write skin geom %s: %s\n", out_path, err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
io.Copy(f, bytes.NewReader(skin.SkinGeometry))
|
||||
}
|
||||
|
||||
func write_skin_texture(name string, skin protocol.Skin) {
|
||||
f, err := os.Create(name + ".png")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing skin: %s\n", err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
skin_tex := image.NewRGBA(image.Rect(0, 0, int(skin.SkinImageWidth), int(skin.SkinImageHeight)))
|
||||
skin_tex.Pix = skin.SkinData
|
||||
|
||||
if err := png.Encode(f, skin_tex); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing skin: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func write_skin_cape(output_path string, skin protocol.Skin) {
|
||||
os.Mkdir(output_path, 0755)
|
||||
f, err := os.Create(path.Join(output_path, "cape.png"))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write skin cape %s: %s\n", out_path, err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
cape_tex := image.NewRGBA(image.Rect(0, 0, int(skin.CapeImageWidth), int(skin.CapeImageHeight)))
|
||||
cape_tex.Pix = skin.CapeData
|
||||
|
||||
if err := png.Encode(f, cape_tex); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing skin: %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func write_skin_animations(output_path string, skin protocol.Skin) {
|
||||
os.Mkdir(output_path, 0755)
|
||||
fmt.Printf("%s has animations (unimplemented)\n", output_path)
|
||||
}
|
||||
|
||||
func write_skin(name string, skin protocol.Skin) {
|
||||
func write_skin(output_path, name string, skin protocol.Skin) {
|
||||
if !strings.HasPrefix(name, player) {
|
||||
return
|
||||
}
|
||||
fmt.Printf("Writing skin for %s\n", name)
|
||||
complex := false
|
||||
skin_dir := path.Join(out_path, name)
|
||||
if len(skin.SkinGeometry) > 0 {
|
||||
write_skin_geometry(skin_dir, skin)
|
||||
complex = true
|
||||
}
|
||||
if len(skin.CapeData) > 0 {
|
||||
write_skin_cape(skin_dir, skin)
|
||||
complex = true
|
||||
}
|
||||
if len(skin.Animations) > 0 {
|
||||
write_skin_animations(skin_dir, skin)
|
||||
complex = true
|
||||
}
|
||||
|
||||
if complex {
|
||||
write_skin_texture(path.Join(skin_dir, "skin"), skin)
|
||||
} else {
|
||||
write_skin_texture(skin_dir, skin)
|
||||
_skin := &Skin{skin}
|
||||
if err := _skin.Write(output_path, name); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error writing skin: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
var player string
|
||||
var player string // who to filter
|
||||
var players = make(map[string]string)
|
||||
var player_counts = make(map[string]int)
|
||||
|
||||
func skin_main(args []string) error {
|
||||
func skin_main(ctx context.Context, args []string) error {
|
||||
var server string
|
||||
var help bool
|
||||
flag.StringVar(&server, "target", "", "target server")
|
||||
flag.StringVar(&server, "server", "", "target server")
|
||||
flag.StringVar(&player, "player", "", "only download the skin of this player")
|
||||
flag.BoolVar(&help, "help", false, "show help")
|
||||
flag.CommandLine.Parse(args)
|
||||
if help {
|
||||
flag.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
if server == "" {
|
||||
server = input_server()
|
||||
}
|
||||
if len(strings.Split(server, ":")) == 1 {
|
||||
server += ":19132"
|
||||
}
|
||||
host, _, err := net.SplitHostPort(server)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Invalid server: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
out_path = fmt.Sprintf("skins/%s", host)
|
||||
hostname, server := server_input(ctx, server)
|
||||
out_path := fmt.Sprintf("skins/%s", hostname)
|
||||
|
||||
var packet_func func(header packet.Header, payload []byte, src, dst net.Addr) = nil
|
||||
if G_debug {
|
||||
packet_func = PacketLogger
|
||||
}
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
<-sigs
|
||||
cancel()
|
||||
println("Exiting...")
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
// connect
|
||||
fmt.Printf("Connecting to %s\n", server)
|
||||
serverConn, err := minecraft.Dialer{
|
||||
|
@ -191,34 +184,30 @@ func skin_main(args []string) error {
|
|||
os.MkdirAll(out_path, 0755)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
pk, err := serverConn.ReadPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
pk, err := serverConn.ReadPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch _pk := pk.(type) {
|
||||
case *packet.PlayerSkin:
|
||||
name := players[_pk.UUID.String()]
|
||||
if name == "" {
|
||||
name = _pk.UUID.String()
|
||||
}
|
||||
switch _pk := pk.(type) {
|
||||
case *packet.PlayerSkin:
|
||||
name := players[_pk.UUID.String()]
|
||||
player_counts[name]++
|
||||
name = fmt.Sprintf("%s_%d", name, player_counts[name])
|
||||
write_skin(out_path, name, _pk.Skin)
|
||||
case *packet.PlayerList:
|
||||
if _pk.ActionType == 1 { // remove
|
||||
continue
|
||||
}
|
||||
for _, player := range _pk.Entries {
|
||||
name := cleanup_name(player.Username)
|
||||
if name == "" {
|
||||
name = _pk.UUID.String()
|
||||
}
|
||||
player_counts[name]++
|
||||
write_skin(fmt.Sprintf("%s_%d", name, player_counts[name]), _pk.Skin)
|
||||
case *packet.PlayerList:
|
||||
if _pk.ActionType == 1 { // remove
|
||||
continue
|
||||
}
|
||||
for _, player := range _pk.Entries {
|
||||
name := cleanup_name(player.Username)
|
||||
if name == "" {
|
||||
name = player.UUID.String()
|
||||
}
|
||||
write_skin(name, player.Skin)
|
||||
players[player.UUID.String()] = name
|
||||
name = player.UUID.String()
|
||||
}
|
||||
write_skin(out_path, name, player.Skin)
|
||||
players[player.UUID.String()] = name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
468
world.go
468
world.go
|
@ -1,29 +1,22 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
|
@ -33,58 +26,61 @@ import (
|
|||
"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
|
||||
)
|
||||
|
||||
const (
|
||||
state_not_connected = iota
|
||||
state_working
|
||||
state_saving
|
||||
state_done
|
||||
)
|
||||
const VIEW_MAP_ID = 0x424242
|
||||
|
||||
var G_window *app.Window
|
||||
var G_state int = state_working
|
||||
var theme = material.NewTheme(gofont.Collection())
|
||||
var finish_button widget.Clickable
|
||||
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),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// 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
|
||||
Dimension *mcdb.Provider
|
||||
Entities []world.SaveableEntity
|
||||
PlayerPos packet.MovePlayer
|
||||
img *image.RGBA
|
||||
chunks map[protocol.ChunkPos]interface{}
|
||||
_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{},
|
||||
Dimension: nil,
|
||||
Entities: make([]world.SaveableEntity, 0),
|
||||
PlayerPos: packet.MovePlayer{},
|
||||
img: image.NewRGBA(image.Rect(0, 0, 128, 128)),
|
||||
chunks: make(map[protocol.ChunkPos]interface{}),
|
||||
_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)
|
||||
func world_main(ctx context.Context, args []string) error {
|
||||
var server string
|
||||
flag.StringVar(&server, "server", "", "target server")
|
||||
flag.CommandLine.Parse(args)
|
||||
if help {
|
||||
if G_help {
|
||||
flag.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
if target == "" {
|
||||
target = input_server()
|
||||
}
|
||||
if len(strings.Split(target, ":")) == 1 {
|
||||
target += ":19132"
|
||||
}
|
||||
_, server = server_input(ctx, server)
|
||||
|
||||
_status := minecraft.NewStatusProvider("Server")
|
||||
listener, err := minecraft.ListenConfig{
|
||||
|
@ -95,33 +91,99 @@ func world_main(args []string) error {
|
|||
}
|
||||
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)
|
||||
}
|
||||
}()
|
||||
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
|
||||
handleConn(ctx, c.(*minecraft.Conn), listener, server)
|
||||
|
||||
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) {
|
||||
if world_state.chunks[pos] != nil {
|
||||
return
|
||||
}
|
||||
|
||||
world_state.chunks[pos] = true
|
||||
min := protocol.ChunkPos{}
|
||||
max := protocol.ChunkPos{}
|
||||
for _ch := range world_state.chunks {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
px_per_chunk := 128 / int(max[0]-min[0]+1)
|
||||
|
||||
world_state.img.Pix = make([]uint8, world_state.img.Rect.Dx()*world_state.img.Rect.Dy()*4)
|
||||
|
||||
{
|
||||
f, _ := os.Create("chunks.json")
|
||||
json.NewEncoder(f).Encode(world_state.chunks)
|
||||
f.Close()
|
||||
}
|
||||
|
||||
for _ch := range world_state.chunks {
|
||||
px_pos := image.Point{X: int(_ch.X() - min.X()), Y: int(_ch.Z() - min.Z())}
|
||||
draw.Draw(
|
||||
world_state.img,
|
||||
image.Rect(
|
||||
px_pos.X*px_per_chunk,
|
||||
px_pos.Y*px_per_chunk,
|
||||
(px_pos.X+1)*px_per_chunk,
|
||||
(px_pos.Y+1)*px_per_chunk,
|
||||
),
|
||||
image.White,
|
||||
image.Point{},
|
||||
draw.Src,
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
f, _ := os.Create("test.png")
|
||||
png.Encode(f, world_state.img)
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func draw_chunk(pos protocol.ChunkPos, ch *chunk.Chunk) {
|
||||
// TODO
|
||||
var _map_send_lock = false
|
||||
|
||||
func get_map_update() *packet.ClientBoundMapItemData {
|
||||
if _map_send_lock {
|
||||
return nil
|
||||
}
|
||||
_map_send_lock = true
|
||||
|
||||
pixels := make([][]color.RGBA, 128)
|
||||
for y := 0; y < 128; y++ {
|
||||
pixels[y] = make([]color.RGBA, 128)
|
||||
for x := 0; x < 128; x++ {
|
||||
pixels[y][x] = world_state.img.At(x, y).(color.RGBA)
|
||||
}
|
||||
}
|
||||
|
||||
_map_send_lock = false
|
||||
return &packet.ClientBoundMapItemData{
|
||||
MapID: VIEW_MAP_ID,
|
||||
Width: 128,
|
||||
Height: 128,
|
||||
Pixels: pixels,
|
||||
UpdateFlags: 2,
|
||||
}
|
||||
}
|
||||
|
||||
func ProcessChunk(pk *packet.LevelChunk) {
|
||||
|
@ -129,10 +191,58 @@ func ProcessChunk(pk *packet.LevelChunk) {
|
|||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
world_state.Dimensions[world_state.Dimension].SaveChunk(world.ChunkPos(pk.Position), ch)
|
||||
world_state.ChunkCount++
|
||||
world_state.Dimension.SaveChunk((world.ChunkPos)(pk.Position), ch)
|
||||
draw_chunk(pk.Position, ch)
|
||||
G_window.Invalidate()
|
||||
}
|
||||
|
||||
var gamemode_ids = map[int32]world.GameMode{
|
||||
0: world.GameModeSurvival,
|
||||
1: world.GameModeCreative,
|
||||
2: world.GameModeAdventure,
|
||||
3: world.GameModeSpectator,
|
||||
4: world.GameModeCreative,
|
||||
}
|
||||
|
||||
var dimension_ids = map[int32]world.Dimension{
|
||||
0: world.Overworld,
|
||||
1: world.Nether,
|
||||
2: world.End,
|
||||
}
|
||||
|
||||
var difficulty_ids = map[int32]world.Difficulty{
|
||||
0: world.DifficultyPeaceful,
|
||||
1: world.DifficultyEasy,
|
||||
2: world.DifficultyNormal,
|
||||
3: world.DifficultyHard,
|
||||
}
|
||||
|
||||
func ProcessStartGame(pk *packet.StartGame) {
|
||||
fmt.Printf("StartGame: %+v\n", pk)
|
||||
dimension, err := mcdb.New(path.Join("worlds", pk.WorldName), dimension_ids[pk.Dimension])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
world_state.Dimension = dimension
|
||||
world_state.chunks = make(map[protocol.ChunkPos]interface{})
|
||||
dimension.SaveSettings(&world.Settings{
|
||||
Name: pk.WorldName,
|
||||
Spawn: cube.Pos{
|
||||
int(pk.WorldSpawn[0]),
|
||||
int(pk.WorldSpawn[1]),
|
||||
int(pk.WorldSpawn[2]),
|
||||
},
|
||||
Time: pk.Time,
|
||||
TimeCycle: true,
|
||||
RainTime: int64(pk.RainLevel), // ?
|
||||
Raining: pk.RainLevel > 0,
|
||||
ThunderTime: int64(pk.LightningLevel),
|
||||
Thundering: pk.LightningLevel > 0,
|
||||
WeatherCycle: true,
|
||||
CurrentTick: pk.Time,
|
||||
DefaultGameMode: gamemode_ids[pk.WorldGameMode],
|
||||
Difficulty: difficulty_ids[pk.Difficulty],
|
||||
TickRange: 6,
|
||||
})
|
||||
}
|
||||
|
||||
func ProcessActor(actor *packet.AddActor) {
|
||||
|
@ -145,51 +255,62 @@ func ProcessBlockUpdate(update *packet.UpdateBlock) {
|
|||
|
||||
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) {
|
||||
func handleConn(ctx context.Context, 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
|
||||
}
|
||||
|
||||
fmt.Printf("Connecting to %s\n", target)
|
||||
serverConn, err := minecraft.Dialer{
|
||||
TokenSource: G_src,
|
||||
ClientData: conn.ClientData(),
|
||||
PacketFunc: packet_func,
|
||||
}.Dial("raknet", target)
|
||||
}.DialContext(ctx, "raknet", target)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
fmt.Fprintf(os.Stderr, "Failed to connect to %s: %s\n", target, err)
|
||||
return
|
||||
}
|
||||
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)
|
||||
errs := make(chan error, 2)
|
||||
go func() {
|
||||
errs <- conn.StartGame(serverConn.GameData())
|
||||
}()
|
||||
go func() {
|
||||
errs <- serverConn.DoSpawn()
|
||||
}()
|
||||
|
||||
// wait for both to finish
|
||||
for i := 0; i < 2; i++ {
|
||||
select {
|
||||
case err := <-errs:
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to start game: %s\n", err)
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
fmt.Fprintf(os.Stderr, "Connection cancelled\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
G_exit = func() {
|
||||
serverConn.Close()
|
||||
conn.Close()
|
||||
listener.Close()
|
||||
world_state.Dimension.Close()
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() { // client loop
|
||||
defer listener.Disconnect(conn, "connection lost")
|
||||
defer serverConn.Close()
|
||||
defer finish_button.Click()
|
||||
defer func() { done <- struct{}{} }()
|
||||
for {
|
||||
skip := false
|
||||
pk, err := conn.ReadPacket()
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -202,20 +323,47 @@ func handleConn(conn *minecraft.Conn, listener *minecraft.Listener, target strin
|
|||
}
|
||||
case *packet.MovePlayer:
|
||||
ProcessMove(_pk)
|
||||
case *packet.MapInfoRequest:
|
||||
fmt.Printf("MapInfoRequest: %d\n", _pk.MapID)
|
||||
if _pk.MapID == VIEW_MAP_ID {
|
||||
if update_pk := get_map_update(); update_pk != nil {
|
||||
conn.WritePacket(update_pk)
|
||||
}
|
||||
skip = true
|
||||
}
|
||||
}
|
||||
|
||||
if err := serverConn.WritePacket(pk); err != nil {
|
||||
if disconnect, ok := errors.Unwrap(err).(minecraft.DisconnectError); ok {
|
||||
_ = listener.Disconnect(conn, disconnect.Error())
|
||||
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
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() { // server loop
|
||||
defer serverConn.Close()
|
||||
defer listener.Disconnect(conn, "connection lost")
|
||||
defer finish_button.Click()
|
||||
defer func() { done <- struct{}{} }()
|
||||
for {
|
||||
pk, err := serverConn.ReadPacket()
|
||||
if err != nil {
|
||||
|
@ -226,17 +374,19 @@ func handleConn(conn *minecraft.Conn, listener *minecraft.Listener, target strin
|
|||
}
|
||||
|
||||
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.StartGame:
|
||||
ProcessStartGame(pk)
|
||||
case *packet.LevelChunk:
|
||||
ProcessChunk(pk)
|
||||
case *packet.SubChunk:
|
||||
ProcessSubChunk(pk)
|
||||
if _pk := get_map_update(); _pk != nil {
|
||||
if err := conn.WritePacket(_pk); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
conn.WritePacket(&packet.Text{
|
||||
TextType: 3,
|
||||
Message: fmt.Sprintf("%d chunks loaded", len(world_state.chunks)),
|
||||
})
|
||||
case *packet.AddActor:
|
||||
ProcessActor(pk)
|
||||
case *packet.UpdateBlock:
|
||||
|
@ -250,97 +400,11 @@ func handleConn(conn *minecraft.Conn, listener *minecraft.Listener, target strin
|
|||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue