diff --git a/config/config.go b/config/config.go index fe00245..5a95eff 100644 --- a/config/config.go +++ b/config/config.go @@ -61,6 +61,12 @@ func defaultConfig() server.ServerConfig { ServerClosed: "Server closed.", OnlineMode: "The server is in online mode.", }, + GUI: server.GUI{ + ServerIP: "0.0.0.0", + ServerPort: 8080, + Password: "ChangeMe", + Enable: false, + }, Tablist: server.Tablist{ Header: []string{}, Footer: []string{}, diff --git a/gui/gui.go b/gui/gui.go index 3ad3b74..582ccfd 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -1,104 +1,42 @@ package gui import ( - "fmt" "io" "net/http" - "strconv" - "strings" - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/app" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/widget" + "github.com/dynamitemc/dynamite/util" ) -var playerCountText *widget.RichText -var playerContainer *widget.List -var consoleText []string -var console *widget.TextGrid -var players = map[int][2]string{} -var indexes = map[string]int{} -var maxPlayers string - -func AddPlayer(name string, uuid string) { - indexes[uuid] = len(players) - players[len(players)] = [2]string{uuid, name} - if playerCountText != nil { - playerCountText.ParseMarkdown(fmt.Sprintf("### %d/%s players", len(players), maxPlayers)) - } - if playerContainer != nil { - playerContainer.Refresh() - } +type logger interface { + Error(string, ...interface{}) + Info(string, ...interface{}) + Debug(string, ...interface{}) } -func RemovePlayer(uuid string) { - delete(players, indexes[uuid]) - if playerCountText != nil { - playerCountText.ParseMarkdown(fmt.Sprintf("### %d/%s players", len(players), maxPlayers)) - } - if playerContainer != nil { - playerContainer.Refresh() - } -} +var log logger -func SetMaxPlayers(max int) { - if max == -1 { - maxPlayers = "Unlimited" - } - maxPlayers = strconv.Itoa(max) - if playerCountText != nil { - playerCountText.ParseMarkdown(fmt.Sprintf("### %d/%s players", len(players), maxPlayers)) - } -} +type handler struct{} -func Log(str string) { - consoleText = append(consoleText, str) - if console != nil { - console.SetText(strings.Join(consoleText, "\n")) +func (handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var code int + switch r.RequestURI { + case "/": + { + io.WriteString(w, "hello there!") + } } + log.Debug("[GUI] [%s] visited %s | Code %d", r.RemoteAddr, r.RequestURI, code) } -func LaunchGUI() { - app := app.New() - window := app.NewWindow("Dynamite Server") - title := widget.NewRichTextFromMarkdown("# Dynamite Server") - consoleTitle := widget.NewRichTextFromMarkdown("## Console") - console = widget.NewTextGridFromString(strings.Join(consoleText, "\n")) - command := widget.NewEntry() - command.SetPlaceHolder("Input a command") - command.OnSubmitted = func(s string) { - //server.Command("console", s) - command.SetText("") +func LaunchGUI(addr string, password string, l logger) { + log = l + if len(password) < 8 && !util.HasArg("-no_password_req") { + log.Error("Failed to start http gui panel. Password must be at least 8 characters long. You can bypass this using -no_password_req") + return + } + log.Info("Launching http gui panel at http://%s", addr) + err := http.ListenAndServe(addr, handler{}) + if err != nil { + log.Error("Failed to start http gui panel: %s", err) } - console := container.NewBorder(consoleTitle, command, nil, nil, container.NewScroll(console)) - - playersTitle := widget.NewRichTextFromMarkdown("## Players") - playerCountText = widget.NewRichTextFromMarkdown(fmt.Sprintf("### %s/%s players", "0", maxPlayers)) - playerContainer = widget.NewList( - func() int { - return len(players) - }, - func() fyne.CanvasObject { - return container.NewHBox() - }, - func(i widget.ListItemID, o fyne.CanvasObject) { - cont := o.(*fyne.Container) - if len(cont.Objects) == 0 { - player := players[i] - res, _ := http.Get(fmt.Sprintf("https://crafatar.com/avatars/%s", player[0])) - skinData, _ := io.ReadAll(res.Body) - skin := widget.NewIcon(fyne.NewStaticResource("skin", skinData)) - skin.Resize(fyne.NewSize(640, 640)) - cont.Objects = append(cont.Objects, skin, widget.NewRichTextFromMarkdown("### "+player[1])) - cont.Refresh() - } - }) - players := container.NewBorder(container.NewVBox(playersTitle, playerCountText), nil, nil, nil, playerContainer) - sp := container.NewHSplit(console, players) - sp.SetOffset(0.6) - window.SetContent(container.NewBorder(title, nil, nil, nil, sp)) - window.Resize(fyne.NewSize(700, 300)) - - window.ShowAndRun() } diff --git a/gui/legacygui.go b/gui/legacygui.go new file mode 100644 index 0000000..e202d06 --- /dev/null +++ b/gui/legacygui.go @@ -0,0 +1,104 @@ +package gui + +import ( + "fmt" + "io" + "net/http" + "strconv" + "strings" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" +) + +var playerCountText *widget.RichText +var playerContainer *widget.List +var consoleText []string +var console *widget.TextGrid +var players = map[int][2]string{} +var indexes = map[string]int{} +var maxPlayers string + +func AddPlayer(name string, uuid string) { + indexes[uuid] = len(players) + players[len(players)] = [2]string{uuid, name} + if playerCountText != nil { + playerCountText.ParseMarkdown(fmt.Sprintf("### %d/%s players", len(players), maxPlayers)) + } + if playerContainer != nil { + playerContainer.Refresh() + } +} + +func RemovePlayer(uuid string) { + delete(players, indexes[uuid]) + if playerCountText != nil { + playerCountText.ParseMarkdown(fmt.Sprintf("### %d/%s players", len(players), maxPlayers)) + } + if playerContainer != nil { + playerContainer.Refresh() + } +} + +func SetMaxPlayers(max int) { + if max == -1 { + maxPlayers = "Unlimited" + } + maxPlayers = strconv.Itoa(max) + if playerCountText != nil { + playerCountText.ParseMarkdown(fmt.Sprintf("### %d/%s players", len(players), maxPlayers)) + } +} + +func Log(str string) { + consoleText = append(consoleText, str) + if console != nil { + console.SetText(strings.Join(consoleText, "\n")) + } +} + +func LaunchLegacyGUI() { + app := app.New() + window := app.NewWindow("Dynamite Server") + title := widget.NewRichTextFromMarkdown("# Dynamite Server") + consoleTitle := widget.NewRichTextFromMarkdown("## Console") + console = widget.NewTextGridFromString(strings.Join(consoleText, "\n")) + command := widget.NewEntry() + command.SetPlaceHolder("Input a command") + command.OnSubmitted = func(s string) { + //server.Command("console", s) + command.SetText("") + } + console := container.NewBorder(consoleTitle, command, nil, nil, container.NewScroll(console)) + + playersTitle := widget.NewRichTextFromMarkdown("## Players") + playerCountText = widget.NewRichTextFromMarkdown(fmt.Sprintf("### %s/%s players", "0", maxPlayers)) + playerContainer = widget.NewList( + func() int { + return len(players) + }, + func() fyne.CanvasObject { + return container.NewHBox() + }, + func(i widget.ListItemID, o fyne.CanvasObject) { + cont := o.(*fyne.Container) + if len(cont.Objects) == 0 { + player := players[i] + res, _ := http.Get(fmt.Sprintf("https://crafatar.com/avatars/%s", player[0])) + skinData, _ := io.ReadAll(res.Body) + skin := widget.NewIcon(fyne.NewStaticResource("skin", skinData)) + skin.Resize(fyne.NewSize(640, 640)) + cont.Objects = append(cont.Objects, skin, widget.NewRichTextFromMarkdown("### "+player[1])) + cont.Refresh() + } + }) + players := container.NewBorder(container.NewVBox(playersTitle, playerCountText), nil, nil, nil, playerContainer) + sp := container.NewHSplit(console, players) + sp.SetOffset(0.6) + window.SetContent(container.NewBorder(title, nil, nil, nil, sp)) + window.Resize(fyne.NewSize(700, 300)) + + window.ShowAndRun() +} diff --git a/logger/logger.go b/logger/logger.go index 3cf9954..be8f38e 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -2,22 +2,13 @@ package logger import ( "fmt" - "os" "time" "github.com/dynamitemc/dynamite/gui" + "github.com/dynamitemc/dynamite/util" "github.com/fatih/color" ) -func HasArg(arg string) bool { - for _, s := range os.Args { - if s == arg { - return true - } - } - return false -} - type Logger struct { FilePath string ConsoleText []string @@ -47,7 +38,7 @@ func (logger *Logger) Print(format string, a ...interface{}) { } func (logger *Logger) Debug(format string, a ...interface{}) { - if !HasArg("-debug") { + if !util.HasArg("-debug") { return } cyan := color.New(color.FgCyan).Add(color.Bold).SprintFunc() diff --git a/main.go b/main.go index 9c16355..46d9330 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "os" "strconv" "time" @@ -9,6 +10,7 @@ import ( "github.com/dynamitemc/dynamite/gui" "github.com/dynamitemc/dynamite/logger" "github.com/dynamitemc/dynamite/server" + "github.com/dynamitemc/dynamite/util" ) var log logger.Logger @@ -34,10 +36,22 @@ func main() { var cfg server.ServerConfig config.LoadConfig("config.toml", &cfg) log.Debug("Loaded config") - if !logger.HasArg("-nogui") { - go start(cfg) - log.Info("Loading GUI panel") - gui.LaunchGUI() + if cfg.GUI.Enable { + if !util.HasArg("-nogui") { + go gui.LaunchGUI(fmt.Sprintf("%s:%d", cfg.GUI.ServerIP, cfg.GUI.ServerPort), cfg.GUI.Password, &log) + } else { + log.Warn("Remove the -nogui argument to load the gui panel") + } + } + if util.HasArg("-uselegacygui") { + if !util.HasArg("-nogui") { + go start(cfg) + log.Info("Loading legacy GUI panel") + gui.LaunchLegacyGUI() + } else { + log.Warn("Remove the -nogui argument to load the legacy gui panel") + start(cfg) + } } else { start(cfg) } diff --git a/server/server_config.go b/server/server_config.go index 249e5e8..03eb50e 100644 --- a/server/server_config.go +++ b/server/server_config.go @@ -2,7 +2,6 @@ package server import ( "os" - "strings" "sync" "github.com/aimjel/minecraft" @@ -10,6 +9,7 @@ import ( "github.com/dynamitemc/dynamite/logger" "github.com/dynamitemc/dynamite/server/player" "github.com/dynamitemc/dynamite/server/world" + "github.com/dynamitemc/dynamite/util" ) type Tablist struct { @@ -17,6 +17,13 @@ type Tablist struct { Footer []string `toml:"footer"` } +type GUI struct { + ServerIP string `toml:"server_ip"` + ServerPort int `toml:"server_port"` + Password string `toml:"password"` + Enable bool `toml:"enable"` +} + type Messages struct { NotInWhitelist string `toml:"not_in_whitelist"` Banned string `toml:"banned"` @@ -51,6 +58,7 @@ type ServerConfig struct { SimulationDistance int `toml:"simulation_distance"` MOTD string `toml:"motd"` Whitelist Whitelist `toml:"whitelist"` + GUI GUI `toml:"gui"` Gamemode string `toml:"gamemode"` Hardcore bool `toml:"hardcore"` MaxPlayers int `toml:"max_players"` @@ -60,17 +68,6 @@ type ServerConfig struct { Messages Messages `toml:"messages"` } -func GetWorldPath() string { - for _, arg := range os.Args { - if strings.HasPrefix(arg, "worldpath=") { - if strings.TrimPrefix(arg, "worldpath=") != "" { - return strings.TrimPrefix(arg, "worldpath=") - } - } - } - return "world" -} - func (cfg *ServerConfig) Listen(address string, logger logger.Logger) (*Server, error) { lnCfg := minecraft.ListenConfig{ Status: minecraft.NewStatus(minecraft.Version{ @@ -91,7 +88,7 @@ func (cfg *ServerConfig) Listen(address string, logger logger.Logger) (*Server, if err != nil { return nil, err } - w, err := world.OpenWorld(GetWorldPath()) + w, err := world.OpenWorld(util.GetArg("worldpath", "world")) if err != nil { logger.Error("Failed to load world: %s", err) os.Exit(1) diff --git a/util/util.go b/util/util.go index d14c9c3..0dfa471 100644 --- a/util/util.go +++ b/util/util.go @@ -6,9 +6,34 @@ import ( "fmt" "io" "net/http" + "os" "strings" ) +func Point[T any](d T) *T { + return &d +} + +func HasArg(arg string) bool { + for _, s := range os.Args { + if s == arg { + return true + } + } + return false +} + +func GetArg(name string, def string) string { + for _, arg := range os.Args { + if strings.HasPrefix(arg, name+"=") { + if s := strings.TrimPrefix(arg, name+"="); s != "" { + return s + } + } + } + return def +} + type Player struct { UUID string `json:"id"` Name string `json:"name"`