diff --git a/NebulaModel/MultiplayerOptions.cs b/NebulaModel/MultiplayerOptions.cs index a9d4cf5fb..0e1c88d1f 100644 --- a/NebulaModel/MultiplayerOptions.cs +++ b/NebulaModel/MultiplayerOptions.cs @@ -129,6 +129,10 @@ public bool StreamerMode [Description("Keyboard shortcut to toggle the chat window")] public KeyboardShortcut ChatHotkey { get; set; } = new(KeyCode.BackQuote, KeyCode.LeftAlt); + [DisplayName("Player List Hotkey")] + [Description("Keyboard shortcut to display the Connected Players Window")] + public KeyboardShortcut PlayerListHotkey { get; set; } = new(KeyCode.BackQuote); + [DisplayName("Auto Open Chat")] [Category("Chat")] [Description("Auto open chat window when receiving message from other players")] diff --git a/NebulaNetwork/Messaging/WebSocketService.cs b/NebulaNetwork/Messaging/WebSocketService.cs index 01521b63d..c96fb5af8 100644 --- a/NebulaNetwork/Messaging/WebSocketService.cs +++ b/NebulaNetwork/Messaging/WebSocketService.cs @@ -91,7 +91,6 @@ protected override void OnError(ErrorEventArgs e) } connections.Remove(Context.UserEndPoint.GetHashCode()); - Log.Info($"Client disconnected because of an error: {ID}, reason: {e.Exception}"); UnityDispatchQueue.RunOnMainThread(() => { diff --git a/NebulaWorld/NebulaWorld.csproj b/NebulaWorld/NebulaWorld.csproj index d39b37d4d..ca630efc9 100644 --- a/NebulaWorld/NebulaWorld.csproj +++ b/NebulaWorld/NebulaWorld.csproj @@ -1,8 +1,8 @@  - - - + + + \ No newline at end of file diff --git a/NebulaWorld/SimulatedWorld.cs b/NebulaWorld/SimulatedWorld.cs index aa889feed..26b5b581a 100644 --- a/NebulaWorld/SimulatedWorld.cs +++ b/NebulaWorld/SimulatedWorld.cs @@ -20,6 +20,7 @@ using NebulaWorld.MonoBehaviours; using NebulaWorld.MonoBehaviours.Local; using NebulaWorld.MonoBehaviours.Local.Chat; +using NebulaWorld.UIPlayerList; using UnityEngine; using UnityEngine.UI; using Object = UnityEngine.Object; @@ -189,6 +190,8 @@ public void SetupInitialPlayerState() localPlayerMovement = GameMain.mainPlayer.gameObject.AddComponentIfMissing(); // ChatManager should exist continuously until the game is closed GameMain.mainPlayer.gameObject.AddComponentIfMissing(); + // Load the Player List Window + GameMain.mainPlayer.gameObject.AddComponentIfMissing(); } public static void FixPlayerAfterImport() diff --git a/NebulaWorld/UIPlayerList/UIPlayerWindow.cs b/NebulaWorld/UIPlayerList/UIPlayerWindow.cs new file mode 100644 index 000000000..0917e3e64 --- /dev/null +++ b/NebulaWorld/UIPlayerList/UIPlayerWindow.cs @@ -0,0 +1,216 @@ +using System; +using System.Linq; +using NebulaModel; +using NebulaModel.Logger; +using NebulaWorld.Combat; +using NebulaWorld.MonoBehaviours.Local.Chat; +using UnityEngine; +using Object = System.Object; + +namespace NebulaWorld.UIPlayerList +{ + public class UIPlayerWindow : MonoBehaviour + { + private const string WindowName = ""; + + private Rect windowSize = new Rect(10f, 10f, 1100f, 600f); + private Vector2 playerListScrollPosition = Vector2.zero; + + private readonly Object _lockable = new Object(); + + private bool _windowVisible; + private ChatWindow _chatWindow; + + public void OnInit() + { + var parent = UIRoot.instance.uiGame.inventoryWindow.transform.parent; + var chatGo = parent.Find("Chat Window") ? parent.Find("Chat Window").gameObject : null; + + if (chatGo != null) + { + _chatWindow = chatGo.transform.GetComponentInChildren(); + } + } + + public void Update() + { + var hasModifier = Config.Options.PlayerListHotkey.Modifiers.Any(); + + _windowVisible = false; + if (Input.GetKey(Config.Options.PlayerListHotkey.MainKey)) + { + if (Config.Options.PlayerListHotkey.Modifiers.All(Input.GetKey)) + { + // If we have no modifier but a modifier is pressed, do not progress + if (!hasModifier && Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.LeftControl) || + Input.GetKey(KeyCode.RightAlt) || Input.GetKey(KeyCode.RightControl)) return; + + _windowVisible = true; + } + } + } + + public void OnGUI() + { + if (!Multiplayer.IsActive) return; + if (Multiplayer.Session.IsDedicated) return; + + try + { + if (!_windowVisible || + IsChatWindowActive() || + UIRoot.instance.uiGame.techTree.active || + UIRoot.instance.uiGame.escMenu.active || + UIRoot.instance.uiGame.dysonEditor.active) + return; + + windowSize = GUI.Window(6245814, windowSize, WindowHandler, WindowName, UIStyles.DialogStyles.WindowBackgroundStyle()); + windowSize.x = (int)(Screen.width * 0.5f - windowSize.width * 0.5f); + windowSize.y = (int)(Screen.height * 0.5f - windowSize.height * 0.5f); + } + catch (Exception ex) + { + Log.Error("Error in UIPlayerWindow OnGUI"); + Log.Error(ex); + } + } + + public void WindowHandler(int id) + { + //used to track how many times a GUI element needs to be recycled in the event + // that an exception is caught + var areaBegins = 0; + var horizontalBegins = 0; + var verticalBegins = 0; + var scrollViewBegins = 0; + + var localPlayer = GameMain.mainPlayer; + + try + { + GUILayout.BeginArea(new Rect(5f, 20f, windowSize.width - 10f, windowSize.height - 55f)); areaBegins++; + GUILayout.BeginHorizontal(); horizontalBegins++; + GUILayout.Space(2); + GUILayout.Label("Online Players", UIStyles.LabelStyles.HeaderLabelStyle, new GUILayoutOption[] { GUILayout.ExpandHeight(false), GUILayout.ExpandWidth(true) }); + GUILayout.EndHorizontal(); horizontalBegins--; + + + // If the player is alone in their save, then do not draw the scoreboard proper + if (AmIAlone()) + { + GUILayout.Label("It's Just You", UIStyles.LabelStyles.CenterLabelLarge, new GUILayoutOption[] { GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true) }); + GUILayout.EndArea(); + return; + } + + GUILayout.Space(20f); + GUILayout.BeginVertical(); verticalBegins++; + + // Headers + GUILayout.BeginHorizontal(); horizontalBegins++; + GUILayout.Label("Name", UIStyles.LabelStyles.RowHeaderLabelsStyle, GUILayout.Width(300)); + GUILayout.Label("Location", UIStyles.LabelStyles.RowHeaderLabelsStyle, GUILayout.Width(600)); + GUILayout.Label("Distance", UIStyles.LabelStyles.RowHeaderLabelsStyle, GUILayout.Width(100)); + GUILayout.EndHorizontal(); horizontalBegins--; + + // Horizontal bar + GUILayout.BeginHorizontal(); horizontalBegins++; + GUILayout.Box("", GUI.skin.horizontalSlider, UIStyles.BoxStyles.HorizontalSliderStyle); + GUILayout.EndHorizontal(); horizontalBegins--; + + GUILayout.Space(10f); + + playerListScrollPosition = GUILayout.BeginScrollView(playerListScrollPosition, + GUILayout.Width(windowSize.width - 10), GUILayout.ExpandHeight(true)); scrollViewBegins++; + + lock (_lockable) + { + using (Multiplayer.Session.World.GetRemotePlayersModels(out var remotePlayersModels)) + { + foreach (var entry in remotePlayersModels) + { + var player = entry.Value; + var pName = player.Username; + var pDistanceFromLocalPlayer = ""; + var pLocation = ""; + + PlanetData pPlanet = null; + if (Multiplayer.Session.Combat.IndexByPlayerId.TryGetValue(player.PlayerId, out var index)) + { + pPlanet = GameMain.galaxy.PlanetById(Multiplayer.Session.Combat.Players[index].planetId); + } + + if (pPlanet != null) + pLocation = $"{pPlanet.displayName}"; + + if (pPlanet == null) + pLocation = "In Space"; + + var pPosition = player.PlayerTransform.position; + var distance = Vector3.Distance(localPlayer.position, pPosition); + + if (distance < 10000) + { + pDistanceFromLocalPlayer = $"{distance:0.00} m"; + } + else if (distance < 600000.0) + { + pDistanceFromLocalPlayer = $"{(distance / 40000):0.00} AU"; + } + else + { + pDistanceFromLocalPlayer = $"{(distance / 2400000.0):0.00} LY"; + } + + GUILayout.BeginHorizontal(); horizontalBegins++; + GUILayout.Label(pName, UIStyles.LabelStyles.RowLabelStyle, GUILayout.Width(300)); + GUILayout.Label(pLocation, UIStyles.LabelStyles.RowLabelStyle, GUILayout.Width(600)); + GUILayout.Label(pDistanceFromLocalPlayer, UIStyles.LabelStyles.RowLabelStyle, + GUILayout.Width(100)); + GUILayout.EndHorizontal(); horizontalBegins--; + GUILayout.Space(10f); + } + } + } + GUILayout.EndScrollView(); scrollViewBegins--; + GUILayout.EndVertical(); verticalBegins--; + GUILayout.EndArea(); areaBegins--; + } + catch (Exception e) + { + Log.Error("Error in UIPlayerWindow OnGUI while building the UI"); + Log.Error(e); + + for (var i = 1; i <= areaBegins; i++) + GUILayout.EndArea(); + + for (var i = 1; i <= horizontalBegins; i++) + GUILayout.EndHorizontal(); + + for (var i = 1; i <= verticalBegins; i++) + GUILayout.EndVertical(); + + for (var i = 1; i <= scrollViewBegins; i++) + GUILayout.EndScrollView(); + } + + } + + private bool AmIAlone() + { + var connectedPlayerCount = 0; + + using (Multiplayer.Session.World.GetRemotePlayersModels(out var remotePlayersModels)) + { + connectedPlayerCount = remotePlayersModels.Count; + } + + return connectedPlayerCount == 0; + } + + private bool IsChatWindowActive() + { + return _chatWindow != null && _chatWindow.IsActive; + } + } +} diff --git a/NebulaWorld/UIPlayerList/UIStyles/BoxStyles.cs b/NebulaWorld/UIPlayerList/UIStyles/BoxStyles.cs new file mode 100644 index 000000000..03d71dc7b --- /dev/null +++ b/NebulaWorld/UIPlayerList/UIStyles/BoxStyles.cs @@ -0,0 +1,12 @@ +using UnityEngine; + +namespace NebulaWorld.UIPlayerList.UIStyles +{ + internal class BoxStyles + { + public static GUILayoutOption[] HorizontalSliderStyle => new GUILayoutOption[] + { + GUILayout.ExpandWidth(true), GUILayout.Height(1), GUILayout.MaxHeight(1) + }; + } +} diff --git a/NebulaWorld/UIPlayerList/UIStyles/DialogStyles.cs b/NebulaWorld/UIPlayerList/UIStyles/DialogStyles.cs new file mode 100644 index 000000000..f7b6a91bb --- /dev/null +++ b/NebulaWorld/UIPlayerList/UIStyles/DialogStyles.cs @@ -0,0 +1,42 @@ +using UnityEngine; + +namespace NebulaWorld.UIPlayerList.UIStyles +{ + internal class DialogStyles + { + // ReSharper disable once InconsistentNaming + private static Texture2D _textureMemory = null; + + public static GUIStyle WindowBackgroundStyle() + { + var width = 32; + var height = 32; + + if (_textureMemory == null) + _textureMemory = MakeTex(width, height, new Color32(36, 67, 76, 200)); + + var style = new GUIStyle(GUI.skin.box) + { + normal = new GUIStyleState() + { + background = _textureMemory + }, + }; + + return style; + } + + private static Texture2D MakeTex(int width, int height, Color col) + { + Color[] pixels = new Color[width * height]; + for (int i = 0; i < pixels.Length; ++i) + { + pixels[i] = col; + } + Texture2D result = new Texture2D(width, height); + result.SetPixels(pixels); + result.Apply(); + return result; + } + } +} diff --git a/NebulaWorld/UIPlayerList/UIStyles/LabelStyles.cs b/NebulaWorld/UIPlayerList/UIStyles/LabelStyles.cs new file mode 100644 index 000000000..1e4d2c3a1 --- /dev/null +++ b/NebulaWorld/UIPlayerList/UIStyles/LabelStyles.cs @@ -0,0 +1,34 @@ +using UnityEngine; + +namespace NebulaWorld.UIPlayerList.UIStyles +{ + internal class LabelStyles + { + public static GUIStyle HeaderLabelStyle = new GUIStyle(UnityEngine.GUI.skin.label) + { + alignment = TextAnchor.MiddleCenter, + fontSize = 24, + fontStyle = FontStyle.Bold + }; + + public static GUIStyle CenterLabelLarge = new GUIStyle(UnityEngine.GUI.skin.label) + { + alignment = TextAnchor.MiddleCenter, + fontSize = 42, + fontStyle = FontStyle.Bold + }; + + public static GUIStyle RowHeaderLabelsStyle = new GUIStyle(UnityEngine.GUI.skin.label) + { + alignment = TextAnchor.MiddleLeft, + fontSize = 18, + fontStyle = FontStyle.Bold + }; + + public static GUIStyle RowLabelStyle = new GUIStyle(UnityEngine.GUI.skin.label) + { + alignment = TextAnchor.MiddleLeft, + fontSize = 18 + }; + } +}