Add Avalonia server-host launcher (MVP scaffold)

Cross-platform C# GUI to replace the Qt/C++ Host GUI, on the same dotnet
toolchain as the server. MVP: load/save commonwealth-server.json, Start/Stop
the CommonwealthOnline.Server process (published exe, then dll, then source
run), and stream its output to a live log. Themed to the Commonwealth Online
brand (near-black + amber). Builds clean on net8.0.

Next: player list + kick/ban via the admin port, then retire the Qt host.
This commit is contained in:
NomadsReach
2026-08-16 18:15:40 -04:00
parent 6861fa4276
commit 1f01e68794
11 changed files with 431 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
using System.Collections.ObjectModel;
using System.IO;
using Avalonia.Threading;
using CommonwealthOnline.Host.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace CommonwealthOnline.Host.ViewModels;
public partial class MainWindowViewModel : ObservableObject
{
private const int MaxLogLines = 2000;
private readonly ServerController _controller = new();
private readonly string _serverDir = System.IO.Directory.GetCurrentDirectory();
private readonly string _configPath =
Path.Combine(System.IO.Directory.GetCurrentDirectory(), "commonwealth-server.json");
[ObservableProperty] private string _serverName;
[ObservableProperty] private string _host;
[ObservableProperty] private int _port;
[ObservableProperty] private int _maxPlayers;
[ObservableProperty] private int _adminPort;
[ObservableProperty] private string _logVerbosity;
[ObservableProperty] private bool _enableGnsTransport;
[ObservableProperty] private bool _isRunning;
[ObservableProperty] private string _statusText = "Stopped";
public ObservableCollection<string> Log { get; } = new();
public string[] VerbosityOptions { get; } = { "error", "warning", "info", "debug" };
public MainWindowViewModel()
{
var config = ServerConfig.Load(_configPath);
_serverName = config.ServerName;
_host = config.Host;
_port = config.Port;
_maxPlayers = config.MaxPlayers;
_adminPort = config.AdminPort;
_logVerbosity = config.LogVerbosity;
_enableGnsTransport = config.EnableGnsTransport;
_controller.LogReceived += line =>
Dispatcher.UIThread.Post(() => Append(line));
_controller.RunningChanged += running =>
Dispatcher.UIThread.Post(() =>
{
IsRunning = running;
StatusText = running ? "Running" : "Stopped";
StartCommand.NotifyCanExecuteChanged();
StopCommand.NotifyCanExecuteChanged();
});
}
private void Append(string line)
{
Log.Add(line);
while (Log.Count > MaxLogLines)
{
Log.RemoveAt(0);
}
}
private ServerConfig CurrentConfig() => new()
{
ServerName = ServerName,
Host = Host,
Port = Port,
MaxPlayers = MaxPlayers,
AdminPort = AdminPort,
LogVerbosity = LogVerbosity,
EnableGnsTransport = EnableGnsTransport,
};
[RelayCommand]
private void Save() => CurrentConfig().Save(_configPath);
[RelayCommand(CanExecute = nameof(CanStart))]
private void Start()
{
Save();
Append($"[host] starting server on {Host}:{Port}...");
_controller.Start(_serverDir, _configPath);
}
private bool CanStart() => !IsRunning;
[RelayCommand(CanExecute = nameof(CanStop))]
private void Stop()
{
Append("[host] stopping server...");
_controller.Stop();
}
private bool CanStop() => IsRunning;
}