public partial class MultiplayerMenu : MarginContainer { enum Status { Disconnected, ConnectionFailed, HostingFailed, Connecting, Connected, Hosting, } Status _status = Status.Disconnected; [Export] public Label StatusLabel { get; set; } [ExportGroup("When Disconnected")] [Export] public Container WhenDisconnected { get; set; } [Export] public LineEdit AddressInput { get; set; } [Export] public SpinBox PortInput { get; set; } [Export] public Button ConnectButton { get; set; } [Export] public Button HostButton { get; set; } [ExportGroup("When Server")] [Export] public Container WhenServer { get; set; } [Export] public LineEdit PortDisplay { get; set; } [ExportGroup("When Connected")] [Export] public Container WhenConnected { get; set; } [Export] public Label PlayersLabel { get; set; } [Export] public Button DisconnectButton { get; set; } public override void _Ready() { PortDisplay.AddThemeColorOverride("font_uneditable_color", PortDisplay.GetThemeColor("font_color")); Multiplayer.ConnectedToServer += () => UpdateStatus(Status.Connected); Multiplayer.ConnectionFailed += () => UpdateStatus(Status.ConnectionFailed); Multiplayer.ServerDisconnected += () => UpdateStatus(Status.Disconnected); Game.Multiplayer.PlayerJoined += _ => UpdatePlayerCount(); Game.Multiplayer.PlayerLeft += _ => UpdatePlayerCount(); } void UpdateStatus(Status status) { _status = status; StatusLabel.Text = status switch { Status.Disconnected => "Disconnected", Status.ConnectionFailed => "Connection failed!", Status.HostingFailed => "Hosting failed!", Status.Connecting => "Connecting ...", Status.Connected => "Connected", Status.Hosting => "Hosting", _ => throw new InvalidOperationException(), }; StatusLabel.LabelSettings.FontColor = (status switch { Status.Disconnected => Colors.DarkGray, Status.ConnectionFailed => Colors.Red, Status.HostingFailed => Colors.Red, Status.Connecting => Colors.Yellow, Status.Connected => Colors.Green, Status.Hosting => Colors.Green, _ => throw new InvalidOperationException(), }).Lerp(StatusLabel.GetThemeColor("font_color"), 0.5f); WhenDisconnected.Visible = status < Status.Connecting; WhenServer.Visible = status == Status.Hosting; WhenConnected.Visible = status >= Status.Connecting; DisconnectButton.Text = status == Status.Hosting ? "Close Server" : "Disconnect"; UpdatePlayerCount(); } void UpdatePlayerCount() { PlayersLabel.Text = _status switch { < Status.Connecting => "Singleplayer", Status.Connecting => "??? Players", > Status.Connecting => ((Func)(() => { var count = Game.Players.Count; return $"{count} {(count != 1 ? "Players" : "Player")}"; }))(), }; } public void OnShowAddressToggled(bool toggledOn) { AddressInput.Secret = !toggledOn; AddressInput.VirtualKeyboardType = toggledOn ? LineEdit.VirtualKeyboardTypeEnum.Default : LineEdit.VirtualKeyboardTypeEnum.Password; } public void OnConnectPressed() { var address = AddressInput.Text; if (address == "") address = AddressInput.PlaceholderText; ushort port; var colonIndex = address.LastIndexOf(':'); if (colonIndex >= 0) { if (!ushort.TryParse(address[(colonIndex+1)..], out port)) port = 0; address = address[..colonIndex]; } else { port = ushort.Parse(AddressInput.PlaceholderText.Split(':')[1]); } Game.Multiplayer.Connect(address, port); UpdateStatus(Status.Connecting); } public void OnHostPressed() { var port = (ushort)RoundToInt(PortInput.Value); if (Game.Multiplayer.CreateServer(port)) { PortDisplay.Text = port.ToString(); UpdateStatus(Status.Hosting); } else UpdateStatus(Status.HostingFailed); } public void OnDisconnectPressed() { Game.Multiplayer.Disconnect(); UpdateStatus(Status.Disconnected); } }