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
+ };
+ }
+}