refactor, world initial saving, ui ingame

This commit is contained in:
olebeck 2022-03-08 23:06:19 +01:00
parent eebc48021a
commit f93f2b794b
3 changed files with 467 additions and 356 deletions

114
main.go
View File

@ -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
View File

@ -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
View File

@ -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
}
}