first commit

This commit is contained in:
2025-07-06 00:23:46 +02:00
commit 38f50c8819
1788 changed files with 112878 additions and 0 deletions

View File

@@ -0,0 +1,182 @@
using System;
using System.Collections;
using System.Linq;
using NitroxClient.Unity.Helper;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.UI;
namespace NitroxClient.MonoBehaviours.Gui.MainMenu;
public class MainMenuNotificationPanel : MonoBehaviour, uGUI_INavigableIconGrid, uGUI_IButtonReceiver
{
public const string NAME = "MultiplayerNotification";
private static MainMenuNotificationPanel instance;
private Image loadingCircle;
private TextMeshProUGUI text;
private GameObject confirmObject;
private Button confirmButton;
private mGUI_Change_Legend_On_Select confirmButtonLegend;
private LegendButtonData[] savedLegendData;
private string returningMenuPanel;
private Action continuationAction;
public static void ShowLoading()
{
if (!instance)
{
Log.Error($"Tried to use {nameof(ShowLoading)} while {nameof(MainMenuNotificationPanel)} was not ready");
return;
}
instance.confirmObject.SetActive(false);
instance.loadingCircle.gameObject.SetActive(true);
instance.text.text = Language.main.Get("Nitrox_Loading");
uGUI_MainMenu.main.ShowPrimaryOptions(true);
MainMenuRightSide.main.OpenGroup(NAME);
instance.confirmButtonLegend.legendButtonConfiguration = [];
}
public static void ShowMessage(string message, string returningMenuPanel, Action continuationAction = null)
{
if (!instance)
{
Log.Error("Tried to use ShowMessage() while MainMenuJoinServerNotificationPanel was not ready");
return;
}
instance.text.text = message;
instance.returningMenuPanel = returningMenuPanel;
instance.continuationAction = continuationAction;
instance.confirmObject.SetActive(true);
instance.loadingCircle.gameObject.SetActive(false);
uGUI_MainMenu.main.ShowPrimaryOptions(true);
MainMenuRightSide.main.OpenGroup(NAME);
instance.confirmButtonLegend.legendButtonConfiguration = instance.savedLegendData;
}
public void Setup(GameObject savedGamesRef)
{
instance = this;
Destroy(transform.RequireGameObject("Scroll View"));
Destroy(GetComponentInChildren<TranslationLiveUpdate>());
text = GetComponentInChildren<TextMeshProUGUI>();
text.horizontalAlignment = HorizontalAlignmentOptions.Center;
text.verticalAlignment = VerticalAlignmentOptions.Top;
text.transform.localPosition = new Vector3(-375, 350, 0);
text.GetComponent<RectTransform>().sizeDelta = new Vector2(350, 280);
GameObject multiplayerButtonRef = savedGamesRef.RequireGameObject("Scroll View/Viewport/SavedGameAreaContent/NewGame");
confirmObject = Instantiate(multiplayerButtonRef, transform, false);
confirmObject.transform.localPosition = new Vector3(-200, 50, 0);
confirmObject.transform.localScale = new Vector3(1.25f, 1.25f, 1.25f);
confirmObject.transform.GetChild(0).GetComponent<RectTransform>().sizeDelta = new Vector2(200, 40);
confirmObject.GetComponentInChildren<TextMeshProUGUI>().text = Language.main.Get("Nitrox_OK");
confirmButton = confirmObject.RequireTransform("NewGameButton").GetComponent<Button>();
confirmButton.onClick = new Button.ButtonClickedEvent();
confirmButton.onClick.AddListener(() =>
{
continuationAction?.Invoke();
if (!string.IsNullOrEmpty(returningMenuPanel))
{
MainMenuRightSide.main.OpenGroup(returningMenuPanel);
}
});
confirmButtonLegend = confirmButton.GetComponent<mGUI_Change_Legend_On_Select>();
savedLegendData = confirmButtonLegend.legendButtonConfiguration.Take(1).ToArray();
GameObject loadingCircleObject = new("LoadingCircle");
loadingCircle = loadingCircleObject.AddComponent<Image>();
loadingCircleObject.transform.SetParent(transform);
loadingCircleObject.transform.localPosition = new Vector3(-200, 180, 0);
loadingCircleObject.transform.localRotation = Quaternion.identity;
loadingCircleObject.transform.localScale = Vector3.one;
}
private IEnumerator Start()
{
AsyncOperationHandle<Texture2D> request = AddressablesUtility.LoadAsync<Texture2D>("Assets/uGUI/Sources/Sprites/HUD/Progress.png");
yield return request;
Texture2D tex = request.Result;
loadingCircle.sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f));
loadingCircle.type = Image.Type.Filled;
}
private void Update()
{
if (loadingCircle)
{
loadingCircle.transform.localRotation = Quaternion.Euler(0, 0, Time.time % 360 * 250); // 250 is the speed
loadingCircle.fillAmount = Mathf.Lerp(0.05f, 0.95f, Math.Abs(Time.time % 6 - 3) * 0.333f); // Lerps t fades from 0 to 1 and back to 0
uGUI_LegendBar.ClearButtons();
}
}
bool uGUI_INavigableIconGrid.ShowSelector => false;
bool uGUI_INavigableIconGrid.EmulateRaycast => false;
bool uGUI_INavigableIconGrid.SelectItemClosestToPosition(Vector3 worldPos) => false;
uGUI_INavigableIconGrid uGUI_INavigableIconGrid.GetNavigableGridInDirection(int dirX, int dirY) => null;
Graphic uGUI_INavigableIconGrid.GetSelectedIcon() => null;
object uGUI_INavigableIconGrid.GetSelectedItem() => confirmObject ? confirmObject : null;
public bool SelectItemInDirection(int dirX, int dirY) => SelectFirstItem();
public bool SelectFirstItem()
{
if (confirmObject)
{
SelectItem(confirmObject);
return true;
}
return false;
}
public void SelectItem(object item)
{
DeselectItem();
GameObject selectedItem = item as GameObject;
if (selectedItem && selectedItem == confirmObject)
{
confirmButton.Select();
confirmButtonLegend.SyncLegendBarToGUISelection();
}
}
public void DeselectItem()
{
if (confirmObject)
{
EventSystem.current.SetSelectedGameObject(null);
confirmObject.GetComponentInChildren<uGUI_BasicColorSwap>().makeTextWhite();
}
uGUI_LegendBar.ClearButtons();
}
public bool OnButtonDown(GameInput.Button button)
{
switch (button)
{
case GameInput.Button.UISubmit:
case GameInput.Button.UICancel:
if (confirmObject.activeSelf)
{
confirmButton.onClick.Invoke();
}
return true;
default:
return false;
}
}
}

View File

@@ -0,0 +1,83 @@
using NitroxClient.MonoBehaviours.Discord;
using NitroxClient.MonoBehaviours.Gui.MainMenu.ServerJoin;
using NitroxClient.MonoBehaviours.Gui.MainMenu.ServersList;
using NitroxClient.Unity.Helper;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
namespace NitroxClient.MonoBehaviours.Gui.MainMenu;
public class NitroxMainMenuModifications : MonoBehaviour
{
private MainMenuRightSide rightSide;
private void OnEnable() => SceneManager.sceneLoaded += SceneManager_sceneLoaded;
private void OnDisable() => SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode loadMode)
{
if (scene.name == "XMenu")
{
rightSide = MainMenuRightSide.main;
MultiplayerMenuMods();
DiscordClient.InitializeRPMenu();
}
}
private void MultiplayerMenuMods()
{
GameObject startButton = GameObjectHelper.RequireGameObject("Menu canvas/Panel/MainMenu/PrimaryOptions/MenuButtons/ButtonPlay");
GameObject showLoadedMultiplayer = Instantiate(startButton, startButton.transform.parent);
showLoadedMultiplayer.name = "ButtonMultiplayer";
showLoadedMultiplayer.transform.SetSiblingIndex(3);
TextMeshProUGUI buttonText = showLoadedMultiplayer.RequireGameObject("Circle/Bar/Text").GetComponent<TextMeshProUGUI>();
buttonText.text = Language.main.Get("Nitrox_Multiplayer");
buttonText.GetComponent<TranslationLiveUpdate>().translationKey = "Nitrox_Multiplayer";
Button showLoadedMultiplayerButton = showLoadedMultiplayer.GetComponent<Button>();
showLoadedMultiplayerButton.onClick = new Button.ButtonClickedEvent();
showLoadedMultiplayerButton.onClick.AddListener(() => rightSide.OpenGroup(MainMenuServerListPanel.NAME));
GameObject savedGamesRef = rightSide.gameObject.RequireGameObject("SavedGames");
GameObject CloneMainMenuLoadPanel(string panelName, string translationKey)
{
GameObject menuPanel = Instantiate(savedGamesRef, rightSide.transform);
menuPanel.name = panelName;
Transform header = menuPanel.RequireTransform("Header");
header.GetComponent<TextMeshProUGUI>().text = Language.main.Get(translationKey);
header.GetComponent<TranslationLiveUpdate>().translationKey = translationKey;
Destroy(menuPanel.RequireGameObject("Scroll View/Viewport/SavedGameAreaContent/NewGame"));
Destroy(menuPanel.GetComponent<MainMenuLoadPanel>());
Destroy(menuPanel.GetComponentInChildren<MainMenuLoadMenu>());
rightSide.groups.Add(menuPanel.GetComponent<MainMenuGroup>());
return menuPanel;
}
GameObject serverJoinNotification = CloneMainMenuLoadPanel(MainMenuNotificationPanel.NAME, string.Empty);
serverJoinNotification.AddComponent<MainMenuNotificationPanel>().Setup(savedGamesRef);
GameObject serverJoin = CloneMainMenuLoadPanel(MainMenuJoinServerPanel.NAME, "Nitrox_JoinServer");
serverJoin.AddComponent<MainMenuJoinServerPanel>().Setup(savedGamesRef);
GameObject serverPasswordEnter = CloneMainMenuLoadPanel(MainMenuEnterPasswordPanel.NAME, "Nitrox_JoinServerPasswordHeader");
serverPasswordEnter.AddComponent<MainMenuEnterPasswordPanel>().Setup(savedGamesRef);
GameObject serverList = CloneMainMenuLoadPanel(MainMenuServerListPanel.NAME, "Nitrox_Multiplayer");
serverList.AddComponent<MainMenuServerListPanel>().Setup(savedGamesRef);
GameObject serverCreate = CloneMainMenuLoadPanel(MainMenuCreateServerPanel.NAME, "Nitrox_AddServer");
serverCreate.AddComponent<MainMenuCreateServerPanel>().Setup(savedGamesRef);
#if RELEASE
// Remove singleplayer button because SP is broken when Nitrox is injected.
// TODO: Allow SP to work and co-exist with Nitrox MP in the future
startButton.SetActive(false);
#endif
}
}

View File

@@ -0,0 +1,186 @@
using System.Collections;
using System.Diagnostics;
using System.Net;
using System.Threading.Tasks;
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.Exceptions;
using NitroxClient.Communication.MultiplayerSession;
using NitroxClient.GameLogic.PlayerLogic.PlayerPreferences;
using NitroxClient.MonoBehaviours.Gui.MainMenu.ServersList;
using NitroxModel.Core;
using NitroxModel.DataStructures.Util;
using NitroxModel.Helper;
using NitroxModel.MultiplayerSession;
using NitroxModel_Subnautica.DataStructures;
using UnityEngine;
namespace NitroxClient.MonoBehaviours.Gui.MainMenu.ServerJoin;
public static class JoinServerBackend
{
private static PlayerPreferenceManager preferencesManager;
private static PlayerPreference activePlayerPreference;
private static IMultiplayerSession multiplayerSession;
private static GameObject multiplayerClient;
private static string serverIp;
private static int serverPort;
public static void RequestSessionReservation(string playerName, Color playerColor)
{
preferencesManager.SetPreference(serverIp, new PlayerPreference(playerName, playerColor));
Optional<string> opPassword = MainMenuEnterPasswordPanel.LastEnteredPassword;
AuthenticationContext authenticationContext = new(playerName, opPassword);
multiplayerSession.RequestSessionReservation(new PlayerSettings(playerColor.ToDto()), authenticationContext);
}
private static void SessionConnectionStateChangedHandler(IMultiplayerSessionConnectionState state)
{
switch (state.CurrentStage)
{
case MultiplayerSessionConnectionStage.ESTABLISHING_SERVER_POLICY:
Log.Info("Requesting session policy info");
Log.InGame(Language.main.Get("Nitrox_RequestingSessionPolicy"));
break;
case MultiplayerSessionConnectionStage.AWAITING_RESERVATION_CREDENTIALS:
Color.RGBToHSV(activePlayerPreference.PreferredColor(), out float hue, out float saturation, out float brightness); // HSV => Hue Saturation Value, HSB => Hue Saturation Brightness
MainMenuJoinServerPanel.Instance.UpdatePlayerPanelValues(activePlayerPreference.PlayerName, new Vector3(hue, saturation, brightness));
if (multiplayerSession.SessionPolicy.RequiresServerPassword)
{
Log.Info("Waiting for server password input");
Log.InGame(Language.main.Get("Nitrox_WaitingPassword"));
MainMenuEnterPasswordPanel.ResetLastEnteredPassword();
MainMenuRightSide.main.OpenGroup(MainMenuEnterPasswordPanel.NAME);
MainMenuEnterPasswordPanel.Instance.FocusPasswordField();
break;
}
Log.Info("Waiting for user input");
Log.InGame(Language.main.Get("Nitrox_WaitingUserInput"));
MainMenuRightSide.main.OpenGroup(MainMenuJoinServerPanel.NAME);
MainMenuJoinServerPanel.Instance.FocusNameInputField();
break;
case MultiplayerSessionConnectionStage.SESSION_RESERVED:
Log.Info("Launching game");
Log.InGame(Language.main.Get("Nitrox_LaunchGame"));
multiplayerSession.ConnectionStateChanged -= SessionConnectionStateChangedHandler;
preferencesManager.Save();
StartGame();
break;
case MultiplayerSessionConnectionStage.SESSION_RESERVATION_REJECTED:
Log.Info("Reservation rejected");
Log.InGame(Language.main.Get("Nitrox_RejectedSessionPolicy"));
MultiplayerSessionReservationState reservationState = multiplayerSession.Reservation.ReservationState;
string reservationRejectionNotification = reservationState.Describe();
MainMenuNotificationPanel.ShowMessage(reservationRejectionNotification, null, () =>
{
multiplayerSession.Disconnect();
multiplayerSession.ConnectAsync(serverIp, serverPort);
});
break;
case MultiplayerSessionConnectionStage.DISCONNECTED:
Log.Info(Language.main.Get("Nitrox_DisconnectedSession"));
break;
}
}
public static async Task StartMultiplayerClientAsync(IPAddress ip, int port)
{
serverIp = ip.ToString();
serverPort = port;
NitroxServiceLocator.BeginNewLifetimeScope();
preferencesManager = NitroxServiceLocator.LocateService<PlayerPreferenceManager>();
activePlayerPreference = preferencesManager.GetPreference(serverIp);
multiplayerSession = NitroxServiceLocator.LocateService<IMultiplayerSession>();
if (!multiplayerClient)
{
multiplayerClient = new GameObject("Nitrox Multiplayer Client");
multiplayerClient.AddComponent<Multiplayer>();
multiplayerSession.ConnectionStateChanged += SessionConnectionStateChangedHandler;
}
try
{
await multiplayerSession.ConnectAsync(serverIp, serverPort);
}
catch (ClientConnectionFailedException ex)
{
Log.ErrorSensitive("Unable to contact the remote server at: {ip}:{port}", serverIp, serverPort);
string msg = $"{Language.main.Get("Nitrox_UnableToConnect")} {serverIp}:{serverPort}";
if (ip.IsLocalhost())
{
if (Process.GetProcessesByName("NitroxServer-Subnautica").Length == 0)
{
Log.Error("No server process was found while address was localhost");
msg += $"\n{Language.main.Get("Nitrox_StartServer")}";
}
else
{
Log.Error(ex);
msg += $"\n{Language.main.Get("Nitrox_FirewallInterfering")}";
}
}
Log.InGame(msg);
StopMultiplayerClient();
MainMenuNotificationPanel.ShowMessage(msg, MainMenuServerListPanel.NAME);
}
}
/// <summary>
/// This method starts a connection with the provided server but leaves handling the session negotiation for the caller.
/// </summary>
public static async Task StartDetachedMultiplayerClientAsync(IPAddress ip, int port, MultiplayerSessionConnectionStateChangedEventHandler sessionHandler)
{
multiplayerClient = new GameObject("Nitrox Multiplayer Client");
Task task = StartMultiplayerClientAsync(ip, port);
multiplayerClient.AddComponent<Multiplayer>();
multiplayerSession.ConnectionStateChanged += sessionHandler;
await task;
}
public static void StartGame()
{
#pragma warning disable CS0618 // God Damn it UWE...
Multiplayer.SubnauticaLoadingStarted();
IEnumerator startNewGame = uGUI_MainMenu.main.StartNewGame(GameMode.Survival);
#pragma warning restore CS0618 // God damn it UWE...
UWE.CoroutineHost.StartCoroutine(startNewGame);
LoadingScreenVersionText.Initialize();
}
public static void StopMultiplayerClient()
{
if (!multiplayerClient || !Multiplayer.Main)
{
return;
}
if (multiplayerSession.CurrentState.CurrentStage != MultiplayerSessionConnectionStage.DISCONNECTED)
{
multiplayerSession.Disconnect();
}
multiplayerSession.ConnectionStateChanged -= SessionConnectionStateChangedHandler;
Multiplayer.Main.StopCurrentSession();
NitroxServiceLocator.EndCurrentLifetimeScope(); //Always do this last.
Object.Destroy(multiplayerClient);
multiplayerClient = null;
}
}

View File

@@ -0,0 +1,59 @@
using NitroxClient.Unity.Helper;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace NitroxClient.MonoBehaviours.Gui.MainMenu.ServerJoin;
public class MainMenuColorPickerPreview : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
private Image previewImage;
private CanvasGroup cg;
public void Init(uGUI_ColorPicker colorPicker)
{
GameObject colorPreview = new("ColorPreview");
colorPreview.transform.SetParent(colorPicker.pointer.transform);
colorPreview.transform.localPosition = new Vector3(-30, 30, 0);
colorPreview.transform.localRotation = Quaternion.identity;
colorPreview.transform.localScale = new Vector3(0.4f, 0.4f, 0.4f);
previewImage = colorPreview.AddComponent<Image>();
previewImage.sprite = CreateCircleSprite();
cg = colorPreview.AddComponent<CanvasGroup>();
cg.alpha = 0;
colorPicker.onColorChange.AddListener(OnColorPickerDrag);
}
private static Sprite CreateCircleSprite()
{
const int HALF_SIZE = 50;
const int RADIUS = 42;
Texture2D tex = new(HALF_SIZE * 2, HALF_SIZE * 2);
for (int y = -HALF_SIZE; y <= HALF_SIZE; y++)
{
for (int x = -HALF_SIZE; x <= HALF_SIZE; x++)
{
bool isInsideCircle = x * x + y * y <= RADIUS * RADIUS;
tex.SetPixel(HALF_SIZE + x, HALF_SIZE + y, isInsideCircle ? Color.white : Color.clear);
}
}
tex.Apply();
return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), 200);
}
private void OnColorPickerDrag(ColorChangeEventData data) => previewImage.color = data.color;
public void OnPointerDown(PointerEventData _)
{
StopAllCoroutines();
StartCoroutine(cg.ShiftAlpha(1, 0.25f, 1.5f, true));
}
public void OnPointerUp(PointerEventData _)
{
StopAllCoroutines();
StartCoroutine(cg.ShiftAlpha(0, 0.25f, 1.5f, false));
}
}

View File

@@ -0,0 +1,251 @@
using System;
using System.Collections;
using System.Linq;
using FMODUnity;
using NitroxClient.MonoBehaviours.Gui.MainMenu.ServersList;
using NitroxClient.Unity.Helper;
using NitroxModel.DataStructures.Util;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace NitroxClient.MonoBehaviours.Gui.MainMenu.ServerJoin;
public class MainMenuEnterPasswordPanel : MonoBehaviour, uGUI_INavigableIconGrid, uGUI_IButtonReceiver
{
public const string NAME = "MultiplayerEnterPassword";
public static MainMenuEnterPasswordPanel Instance { get; private set; }
private TMP_InputField passwordInput;
private mGUI_Change_Legend_On_Select legendChange;
private GameObject selectedItem;
private GameObject[] selectableItems;
private static string lastEnteredPassword;
public static Optional<string> LastEnteredPassword => lastEnteredPassword != null ? Optional.Of(lastEnteredPassword) : Optional.Empty;
public static void ResetLastEnteredPassword() => lastEnteredPassword = null;
public void Setup(GameObject savedGamesRef)
{
Instance = this;
GameObject multiplayerButtonRef = savedGamesRef.RequireGameObject("Scroll View/Viewport/SavedGameAreaContent/NewGame");
GameObject generalTextRef = multiplayerButtonRef.GetComponentInChildren<TextMeshProUGUI>().gameObject;
GameObject inputFieldRef = GameObject.Find("/Menu canvas/Panel/MainMenu/RightSide/Home/EmailBox/InputField");
GameObject passwordInputGameObject = Instantiate(inputFieldRef, transform, false);
passwordInputGameObject.transform.localPosition = new Vector3(-160, 300, 0);
passwordInputGameObject.GetComponent<RectTransform>().sizeDelta = new Vector2(300, 40);
passwordInput = passwordInputGameObject.GetComponent<TMP_InputField>();
passwordInput.characterValidation = TMP_InputField.CharacterValidation.None;
passwordInput.onSubmit = new TMP_InputField.SubmitEvent();
passwordInput.onSubmit.AddListener(_ => OnConfirmButtonClicked());
passwordInput.placeholder.GetComponent<TranslationLiveUpdate>().translationKey = Language.main.Get("Nitrox_JoinServerPlaceholder");
GameObject passwordInputDesc = Instantiate(generalTextRef, passwordInputGameObject.transform, false);
passwordInputDesc.transform.localPosition = new Vector3(-200, 0, 0);
passwordInputDesc.GetComponent<TextMeshProUGUI>().text = Language.main.Get("Nitrox_JoinServerPassword");
GameObject confirmButton = Instantiate(multiplayerButtonRef, transform, false);
confirmButton.transform.localPosition = new Vector3(-200, 90, 0);
confirmButton.transform.GetChild(0).GetComponent<RectTransform>().sizeDelta = new Vector2(200, 40);
confirmButton.GetComponentInChildren<TextMeshProUGUI>().text = Language.main.Get("Nitrox_Confirm");
Button confirmButtonButton = confirmButton.RequireTransform("NewGameButton").GetComponent<Button>();
confirmButtonButton.onClick = new Button.ButtonClickedEvent();
confirmButtonButton.onClick.AddListener(OnConfirmButtonClicked);
GameObject backButton = Instantiate(multiplayerButtonRef, transform, false);
backButton.transform.localPosition = new Vector3(-200, 40, 0);
backButton.transform.GetChild(0).GetComponent<RectTransform>().sizeDelta = new Vector2(200, 40);
backButton.GetComponentInChildren<TextMeshProUGUI>().text = Language.main.Get("Nitrox_Cancel");
Button backButtonButton = backButton.RequireTransform("NewGameButton").GetComponent<Button>();
backButtonButton.onClick = new Button.ButtonClickedEvent();
backButtonButton.onClick.AddListener(OnCancelClick);
selectableItems = [passwordInputGameObject, confirmButton, backButton];
Destroy(transform.Find("Scroll View").gameObject);
legendChange = gameObject.AddComponent<mGUI_Change_Legend_On_Select>();
legendChange.legendButtonConfiguration = confirmButtonButton.GetComponent<mGUI_Change_Legend_On_Select>().legendButtonConfiguration.Take(1).ToArray();
}
public void FocusPasswordField()
{
StartCoroutine(Coroutine());
IEnumerator Coroutine()
{
passwordInput.Select();
EventSystem.current.SetSelectedGameObject(passwordInput.gameObject);
yield return null;
passwordInput.MoveToEndOfLine(false, true);
}
}
private void OnConfirmButtonClicked()
{
lastEnteredPassword = passwordInput.text;
MainMenuRightSide.main.OpenGroup(MainMenuJoinServerPanel.NAME);
MainMenuJoinServerPanel.Instance.FocusNameInputField();
}
private static void OnCancelClick()
{
JoinServerBackend.StopMultiplayerClient();
MainMenuRightSide.main.OpenGroup(MainMenuServerListPanel.NAME);
}
public bool OnButtonDown(GameInput.Button button)
{
switch (button)
{
case GameInput.Button.UISubmit:
OnConfirm();
return true;
case GameInput.Button.UICancel:
OnBack();
return true;
default:
return false;
}
}
public void OnConfirm()
{
if (selectedItem.TryGetComponentInChildren(out TMP_InputField inputField))
{
inputField.ActivateInputField();
}
if (selectedItem.TryGetComponentInChildren(out Button button))
{
button.onClick.Invoke();
}
}
public void OnBack()
{
passwordInput.text = string.Empty;
ResetLastEnteredPassword();
DeselectAllItems();
MainMenuRightSide.main.OpenGroup(MainMenuServerListPanel.NAME);
}
bool uGUI_INavigableIconGrid.ShowSelector => false;
bool uGUI_INavigableIconGrid.EmulateRaycast => false;
bool uGUI_INavigableIconGrid.SelectItemClosestToPosition(Vector3 worldPos) => false;
uGUI_INavigableIconGrid uGUI_INavigableIconGrid.GetNavigableGridInDirection(int dirX, int dirY) => null;
Graphic uGUI_INavigableIconGrid.GetSelectedIcon() => null;
object uGUI_INavigableIconGrid.GetSelectedItem() => selectedItem;
public void SelectItem(object item)
{
DeselectItem();
selectedItem = item as GameObject;
legendChange.SyncLegendBarToGUISelection();
if (!selectedItem)
{
return;
}
if (selectedItem.TryGetComponent(out TMP_InputField selectedInputField))
{
selectedInputField.Select();
}
else // Buttons
{
selectedItem.GetComponentInChildren<uGUI_BasicColorSwap>().makeTextBlack();
selectedItem.transform.GetChild(0).GetComponent<Image>().sprite = MainMenuServerListPanel.SelectedSprite;
}
if (!EventSystem.current.alreadySelecting)
{
EventSystem.current.SetSelectedGameObject(selectedItem);
}
RuntimeManager.PlayOneShot(MainMenuServerListPanel.HoverSound.path);
}
public void DeselectItem()
{
if (!selectedItem)
{
return;
}
if (selectedItem.TryGetComponent(out TMP_InputField selectedInputField))
{
selectedInputField.DeactivateInputField();
selectedInputField.ReleaseSelection();
}
else // Buttons
{
selectedItem.GetComponentInChildren<uGUI_BasicColorSwap>().makeTextWhite();
selectedItem.transform.GetChild(0).GetComponent<Image>().sprite = MainMenuServerListPanel.NormalSprite;
}
if (!EventSystem.current.alreadySelecting)
{
EventSystem.current.SetSelectedGameObject(null);
}
selectedItem = null;
}
public void DeselectAllItems()
{
foreach (GameObject child in selectableItems)
{
selectedItem = child;
DeselectItem();
}
}
public bool SelectFirstItem()
{
SelectItem(selectableItems[0]);
return true;
}
public bool SelectItemInDirection(int dirX, int dirY)
{
if (!selectedItem)
{
return SelectFirstItem();
}
if (dirX == dirY)
{
return false;
}
int dir = dirX + dirY > 0 ? 1 : -1;
for (int newIndex = GetSelectedIndex() + dir; newIndex >= 0 && newIndex < selectableItems.Length; newIndex += dir)
{
if (SelectItemByIndex(newIndex))
{
return true;
}
}
return false;
}
private int GetSelectedIndex() => selectedItem ? Array.IndexOf(selectableItems, selectedItem) : -1;
private bool SelectItemByIndex(int selectedIndex)
{
if (selectedIndex >= 0 && selectedIndex < selectableItems.Length)
{
SelectItem(selectableItems[selectedIndex]);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,363 @@
using System.Collections;
using System.Linq;
using System.Text.RegularExpressions;
using FMODUnity;
using NitroxClient.MonoBehaviours.Gui.MainMenu.ServersList;
using NitroxClient.Unity.Helper;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.UI;
using UWE;
namespace NitroxClient.MonoBehaviours.Gui.MainMenu.ServerJoin;
public class MainMenuJoinServerPanel : MonoBehaviour, uGUI_INavigableIconGrid, uGUI_IButtonReceiver, uGUI_IScrollReceiver, uGUI_IAdjustReceiver
{
public const string NAME = "MultiplayerJoinServer";
public static MainMenuJoinServerPanel Instance { get; private set; }
private GameObject playerSettingsPanel;
private TextMeshProUGUI header;
private uGUI_ColorPicker colorPicker;
private MainMenuColorPickerPreview colorPickerPreview;
private Slider saturationSlider;
private uGUI_InputField playerNameInputField;
private GameObject selectedItem;
private GameObject[] selectableItems;
public void Setup(GameObject savedGamesRef)
{
Instance = this;
Destroy(transform.RequireGameObject("Scroll View"));
Destroy(GetComponentInChildren<TranslationLiveUpdate>());
header = GetComponentInChildren<TextMeshProUGUI>();
CoroutineHost.StartCoroutine(AsyncSetup(savedGamesRef)); // As JoinServer waits for AsyncSetup to be completed we can't use normal Unity IEnumerator Start()
}
private IEnumerator AsyncSetup(GameObject savedGamesRef)
{
AsyncOperationHandle<GameObject> request = AddressablesUtility.LoadAsync<GameObject>("Assets/Prefabs/Base/GeneratorPieces/BaseMoonpoolUpgradeConsole.prefab");
yield return request;
GameObject colorPickerPanelPrototype = request.Result.RequireGameObject("EditScreen/Active");
RectTransform parent = GetComponent<RectTransform>();
GameObject newGameButtonRef = savedGamesRef.RequireGameObject("Scroll View/Viewport/SavedGameAreaContent/NewGame/NewGameButton");
LegendButtonData[] defaultLegend = newGameButtonRef.GetComponent<mGUI_Change_Legend_On_Select>().legendButtonConfiguration.Take(1).ToArray();
//Create a clone of the RocketBase color picker panel.
playerSettingsPanel = Instantiate(colorPickerPanelPrototype, parent);
//Prepares name input field
GameObject inputField = playerSettingsPanel.RequireGameObject("InputField");
inputField.transform.SetParent(parent);
inputField.transform.localPosition = new Vector3(-200, 310, 0);
inputField.transform.localScale = Vector3.one;
inputField.AddComponent<mGUI_Change_Legend_On_Select>().legendButtonConfiguration = defaultLegend;
playerNameInputField = inputField.GetComponent<uGUI_InputField>();
((TextMeshProUGUI)playerNameInputField.placeholder).text = Language.main.Get("Nitrox_EnterName");
playerNameInputField.textComponent.fontSizeMin = 17;
playerNameInputField.textComponent.fontSizeMax = 21;
playerNameInputField.textComponent.GetComponent<RectTransform>().sizeDelta = new Vector2(-20, 42);
playerNameInputField.characterLimit = 25; // See this.OnJoinClick()
playerNameInputField.onFocusSelectAll = false;
playerNameInputField.onSubmit.AddListener(_ => OnJoinClick());
playerNameInputField.onSubmit.AddListener(_ => DeselectAllItems());
playerNameInputField.ActivateInputField();
//Prepares player color picker
GameObject colorPickerObject = playerSettingsPanel.RequireGameObject("ColorPicker");
colorPickerObject.transform.SetParent(parent);
colorPickerObject.transform.localPosition = new Vector3(-268, 175, 0);
colorPickerObject.transform.localScale = new Vector3(1.1f, 0.75f, 1);
colorPicker = colorPickerObject.GetComponentInChildren<uGUI_ColorPicker>();
colorPicker.pointer.localScale = new Vector3(1f, 1.46f, 1);
saturationSlider = colorPicker.saturationSlider;
saturationSlider.transform.localPosition = new Vector3(197, 0, 0);
colorPickerPreview = colorPicker.gameObject.AddComponent<MainMenuColorPickerPreview>();
colorPickerPreview.Init(colorPicker);
GameObject buttonLeft = Instantiate(newGameButtonRef, parent);
buttonLeft.GetComponent<RectTransform>().sizeDelta = new Vector2(160, 45);
buttonLeft.GetComponent<mGUI_Change_Legend_On_Select>().legendButtonConfiguration = defaultLegend;
GameObject buttonRight = Instantiate(buttonLeft, parent);
//Prepares cancel button
buttonLeft.transform.SetParent(parent);
buttonLeft.transform.localPosition = new Vector3(-285, 40, 0);
buttonLeft.GetComponentInChildren<TextMeshProUGUI>().text = Language.main.Get("Nitrox_Cancel");
Button cancelButton = buttonLeft.GetComponent<Button>();
cancelButton.onClick = new Button.ButtonClickedEvent();
cancelButton.onClick.AddListener(OnCancelClick);
cancelButton.onClick.AddListener(DeselectAllItems);
//Prepares join button
buttonRight.transform.localPosition = new Vector3(-115, 40, 0);
buttonRight.GetComponentInChildren<TextMeshProUGUI>().text = Language.main.Get("Nitrox_Join");
Button joinButton = buttonRight.GetComponent<Button>();
joinButton.onClick = new Button.ButtonClickedEvent();
joinButton.onClick.AddListener(OnJoinClick);
joinButton.onClick.AddListener(DeselectAllItems);
selectableItems = [inputField, colorPicker.gameObject, saturationSlider.gameObject, buttonLeft, buttonRight];
Destroy(playerSettingsPanel);
}
private void OnJoinClick()
{
string playerName = playerNameInputField.text;
//https://regex101.com/r/eTWiEs/2/
if (!Regex.IsMatch(playerName, "^[a-zA-Z0-9._-]{3,25}$"))
{
MainMenuNotificationPanel.ShowMessage(Language.main.Get("Nitrox_InvalidUserName"), NAME);
return;
}
JoinServerBackend.RequestSessionReservation(playerName, colorPicker.currentColor);
}
private static void OnCancelClick()
{
JoinServerBackend.StopMultiplayerClient();
MainMenuRightSide.main.OpenGroup(MainMenuServerListPanel.NAME);
}
public void UpdatePanelValues(string serverName) => header.text = $" {Language.main.Get("Nitrox_JoinServer")} {serverName}";
public void UpdatePlayerPanelValues(string playerName, Vector3 hsb)
{
playerNameInputField.text = playerName;
colorPicker.SetHSB(hsb);
}
public void FocusNameInputField()
{
StartCoroutine(Coroutine());
IEnumerator Coroutine()
{
SelectFirstItem();
yield return new WaitForEndOfFrame();
playerNameInputField.MoveToEndOfLine(false, true);
}
}
public bool OnButtonDown(GameInput.Button button)
{
if (button != GameInput.Button.UISubmit || !selectedItem)
{
return false;
}
if (selectedItem.TryGetComponentInChildren(out TMP_InputField inputField))
{
inputField.Select();
inputField.ActivateInputField();
}
else if (selectedItem.TryGetComponentInChildren(out Button buttonComponent))
{
buttonComponent.onClick.Invoke();
}
return true;
}
public bool OnScroll(float scrollDelta, float speedMultiplier)
{
if (EventSystem.current != null &&
EventSystem.current.currentSelectedGameObject == selectedItem &&
selectedItem.TryGetComponent(out Slider slider))
{
slider.value += scrollDelta * speedMultiplier * 0.01f;
return true;
}
return false;
}
public bool OnAdjust(Vector2 adjustDelta)
{
if (selectedItem && selectedItem.TryGetComponent(out uGUI_ColorPicker selectedColorPicker))
{
return selectedColorPicker.OnAdjust(adjustDelta);
}
return false;
}
object uGUI_INavigableIconGrid.GetSelectedItem() => selectedItem;
bool uGUI_INavigableIconGrid.ShowSelector => false;
bool uGUI_INavigableIconGrid.EmulateRaycast => false;
bool uGUI_INavigableIconGrid.SelectItemClosestToPosition(Vector3 worldPos) => false;
uGUI_INavigableIconGrid uGUI_INavigableIconGrid.GetNavigableGridInDirection(int dirX, int dirY) => null;
Graphic uGUI_INavigableIconGrid.GetSelectedIcon() => null;
public void SelectItem(object item)
{
DeselectItem();
selectedItem = item as GameObject;
if (!selectedItem)
{
return;
}
if (selectedItem.TryGetComponent(out mGUI_Change_Legend_On_Select changeLegend))
{
changeLegend.SyncLegendBarToGUISelection();
}
else
{
uGUI_LegendBar.ClearButtons();
}
if (selectedItem == selectableItems[1])
{
colorPicker.pointer.GetComponent<Image>().color = Color.cyan;
if (GameInput.GetPrimaryDevice() == GameInput.Device.Controller)
{
colorPickerPreview.OnPointerDown(null);
}
}
else if (selectedItem == selectableItems[3] || selectedItem == selectableItems[4])
{
selectedItem.GetComponentInChildren<uGUI_BasicColorSwap>().makeTextBlack();
}
if (selectedItem.TryGetComponentInChildren(out Selectable selectable))
{
selectable.Select();
}
if (!EventSystem.current.alreadySelecting)
{
EventSystem.current.SetSelectedGameObject(selectedItem);
}
RuntimeManager.PlayOneShot(MainMenuServerListPanel.HoverSound.path);
}
public void DeselectItem()
{
if (!selectedItem)
{
return;
}
if (selectedItem.TryGetComponent(out TMP_InputField selectedInputField))
{
//This line need to be before selectedInputField.ReleaseSelection() as it will call this method recursive leading to NRE
selectedInputField.DeactivateInputField();
selectedInputField.ReleaseSelection();
}
else if (selectedItem.TryGetComponent(out uGUI_ColorPicker selectedColorPicker))
{
Image colorPickerPointer = selectedColorPicker.pointer.GetComponent<Image>();
if (colorPickerPointer.color != Color.white &&
GameInput.GetPrimaryDevice() == GameInput.Device.Controller)
{
colorPickerPreview.OnPointerUp(null);
}
colorPickerPointer.color = Color.white;
}
else if (selectedItem.TryGetComponentInChildren(out uGUI_BasicColorSwap colorSwap))
{
colorSwap.makeTextWhite();
}
if (!EventSystem.current.alreadySelecting)
{
EventSystem.current.SetSelectedGameObject(null);
}
selectedItem = null;
}
public void DeselectAllItems()
{
foreach (GameObject item in selectableItems)
{
selectedItem = item;
DeselectItem();
}
}
public bool SelectFirstItem()
{
SelectItem(selectableItems[0]);
return true;
}
public bool SelectItemInDirection(int dirX, int dirY)
{
if (!selectedItem)
{
return SelectFirstItem();
}
if (selectedItem == selectableItems[0]) //Name input
{
switch (dirY)
{
case < 0:
SelectItem(selectableItems[^2]);
return true;
case > 0:
SelectItem(selectableItems[1]);
return true;
}
}
if (selectedItem == selectableItems[1] || selectedItem == selectableItems[2]) // ColorPicker and SaturationSlider
{
switch (dirY)
{
case < 0:
SelectItem(selectableItems[0]);
return true;
case > 0:
SelectItem(selectableItems[3]);
return true;
}
if (dirX != 0)
{
int direction = selectedItem == selectableItems[1] ? 0 : 1;
direction = (direction + dirX) % 2;
SelectItem(selectableItems[1 + direction]);
return true;
}
}
if (selectedItem == selectableItems[3] || selectedItem == selectableItems[4]) // CancelButton and ConfirmButton
{
switch (dirY)
{
case < 0:
SelectItem(selectableItems[1]);
return true;
case > 0:
SelectItem(selectableItems[0]);
return true;
}
if (dirX != 0)
{
int direction = selectedItem == selectableItems[3] ? 0 : 1;
direction = (direction + dirX) % 2;
SelectItem(selectableItems[3 + direction]);
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,292 @@
using System;
using System.Collections;
using System.Linq;
using FMODUnity;
using NitroxClient.Unity.Helper;
using NitroxModel.Serialization;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace NitroxClient.MonoBehaviours.Gui.MainMenu.ServersList;
public class MainMenuCreateServerPanel : MonoBehaviour, uGUI_INavigableIconGrid, uGUI_IButtonReceiver
{
public const string NAME = "MultiplayerCreateServer";
private const string DEFAULT_PORT = "11000";
private TMP_InputField serverNameInput, serverAddressInput, serverPortInput;
private mGUI_Change_Legend_On_Select legendChange;
private GameObject selectedItem;
private GameObject[] selectableItems;
public void Setup(GameObject savedGamesRef)
{
GameObject multiplayerButtonRef = savedGamesRef.RequireGameObject("Scroll View/Viewport/SavedGameAreaContent/NewGame");
GameObject generalTextRef = multiplayerButtonRef.GetComponentInChildren<TextMeshProUGUI>().gameObject;
GameObject inputFieldRef = GameObject.Find("/Menu canvas/Panel/MainMenu/RightSide/Home/EmailBox/InputField");
GameObject inputFieldBlueprint = Instantiate(inputFieldRef, transform, false);
inputFieldBlueprint.GetComponent<RectTransform>().sizeDelta = new Vector2(300, 40);
TMP_InputField inputFieldBlueprintInput = inputFieldBlueprint.GetComponent<TMP_InputField>();
inputFieldBlueprintInput.characterValidation = TMP_InputField.CharacterValidation.None;
inputFieldBlueprintInput.onSubmit = new TMP_InputField.SubmitEvent();
inputFieldBlueprintInput.onSubmit.AddListener(_ => { SelectItemInDirection(0, 1); });
GameObject serverName = Instantiate(inputFieldBlueprint, transform, false);
serverName.transform.localPosition = new Vector3(-160, 300, 0);
serverNameInput = serverName.GetComponent<TMP_InputField>();
serverNameInput.placeholder.GetComponent<TranslationLiveUpdate>().translationKey = Language.main.Get("Nitrox_AddServer_NamePlaceholder");
GameObject serverNameDesc = Instantiate(generalTextRef, serverName.transform, false);
serverNameDesc.transform.localPosition = new Vector3(-200, 0, 0);
serverNameDesc.GetComponent<TextMeshProUGUI>().text = Language.main.Get("Nitrox_AddServer_NameDescription");
GameObject serverAddress = Instantiate(inputFieldBlueprint, transform, false);
serverAddress.transform.localPosition = new Vector3(-160, 225, 0);
serverAddressInput = serverAddress.GetComponent<TMP_InputField>();
serverAddressInput.placeholder.GetComponent<TranslationLiveUpdate>().translationKey = Language.main.Get("Nitrox_AddServer_AddressPlaceholder");
GameObject serverAddressDesc = Instantiate(generalTextRef, serverAddress.transform, false);
serverAddressDesc.transform.localPosition = new Vector3(-200, 0, 0);
serverAddressDesc.GetComponent<TextMeshProUGUI>().text = Language.main.Get("Nitrox_AddServer_AddressDescription");
GameObject serverPort = Instantiate(inputFieldBlueprint, transform, false);
serverPort.transform.localPosition = new Vector3(-160, 150, 0);
serverPortInput = serverPort.GetComponent<TMP_InputField>();
serverPortInput.characterValidation = TMP_InputField.CharacterValidation.Integer;
serverPortInput.placeholder.GetComponent<TranslationLiveUpdate>().translationKey = Language.main.Get("Nitrox_AddServer_PortPlaceholder");
serverPortInput.text = DEFAULT_PORT;
GameObject serverPortDesc = Instantiate(generalTextRef, serverPort.transform, false);
serverPortDesc.transform.localPosition = new Vector3(-200, 0, 0);
serverPortDesc.GetComponent<TextMeshProUGUI>().text = Language.main.Get("Nitrox_AddServer_PortDescription");
GameObject confirmButton = Instantiate(multiplayerButtonRef, transform, false);
confirmButton.transform.localPosition = new Vector3(-200, 90, 0);
confirmButton.transform.GetChild(0).GetComponent<RectTransform>().sizeDelta = new Vector2(200, 40);
confirmButton.GetComponentInChildren<TextMeshProUGUI>().text = Language.main.Get("Nitrox_AddServer_Confirm");
Button confirmButtonButton = confirmButton.RequireTransform("NewGameButton").GetComponent<Button>();
confirmButtonButton.onClick = new Button.ButtonClickedEvent();
confirmButtonButton.onClick.AddListener(SaveServer);
GameObject backButton = Instantiate(multiplayerButtonRef, transform, false);
backButton.transform.localPosition = new Vector3(-200, 40, 0);
backButton.transform.GetChild(0).GetComponent<RectTransform>().sizeDelta = new Vector2(200, 40);
backButton.GetComponentInChildren<TextMeshProUGUI>().text = Language.main.Get("Nitrox_Cancel");
Button backButtonButton = backButton.RequireTransform("NewGameButton").GetComponent<Button>();
backButtonButton.onClick = new Button.ButtonClickedEvent();
backButtonButton.onClick.AddListener(OnBack);
selectableItems = [serverName, serverAddress, serverPort, confirmButton, backButton];
Destroy(inputFieldBlueprint);
Destroy(transform.Find("Scroll View").gameObject);
legendChange = gameObject.AddComponent<mGUI_Change_Legend_On_Select>();
legendChange.legendButtonConfiguration = confirmButtonButton.GetComponent<mGUI_Change_Legend_On_Select>().legendButtonConfiguration.Take(2).ToArray();
}
private void SaveServer()
{
string serverNameText = serverNameInput.text.Trim();
string serverHostText = serverAddressInput.text.Trim();
string serverPortText = serverPortInput.text.Trim();
if (string.IsNullOrWhiteSpace(serverNameText) ||
string.IsNullOrWhiteSpace(serverHostText) ||
string.IsNullOrWhiteSpace(serverPortText) ||
!int.TryParse(serverPortText, out int serverPort))
{
Log.InGame(Language.main.Get("Nitrox_AddServer_InvalidInput"));
return;
}
GameObject newEntry = MainMenuServerListPanel.Main.CreateServerButton(serverNameText, serverHostText, serverPort);
ServerList.Instance.Add(new ServerList.Entry(serverNameText, serverHostText, serverPort));
ServerList.Instance.Save();
OnBack();
MainMenuServerListPanel.Main.StartCoroutine(DelayedScrollToNewEntry());
Log.InGame(Language.main.Get("Nitrox_AddServer_CreatedSuccessful"));
return;
IEnumerator DelayedScrollToNewEntry()
{
yield return new WaitForEndOfFrame();
UIUtils.ScrollToShowItemInCenter(newEntry.transform);
}
}
public bool OnButtonDown(GameInput.Button button)
{
switch (button)
{
case GameInput.Button.UISubmit:
OnConfirm();
return true;
case GameInput.Button.UICancel:
OnBack();
return true;
default:
return false;
}
}
private void Update()
{
if (GameInput.GetKeyDown(KeyCode.Tab))
{
if (GameInput.GetKey(KeyCode.LeftShift))
{
SelectItemInDirection(-1, 0);
}
else
{
SelectItemInDirection(1, 0);
}
}
else if (selectedItem && GameInput.GetKeyDown(KeyCode.Return))
{
OnConfirm();
}
}
public void OnBack()
{
serverNameInput.text = string.Empty;
serverAddressInput.text = string.Empty;
serverPortInput.text = string.Empty;
DeselectAllItems();
MainMenuRightSide.main.OpenGroup(MainMenuServerListPanel.NAME);
}
public void OnConfirm()
{
if (selectedItem.TryGetComponentInChildren(out TMP_InputField inputField))
{
inputField.ActivateInputField();
}
if (selectedItem.TryGetComponentInChildren(out Button button))
{
button.onClick.Invoke();
}
}
object uGUI_INavigableIconGrid.GetSelectedItem() => selectedItem;
bool uGUI_INavigableIconGrid.ShowSelector => false;
bool uGUI_INavigableIconGrid.EmulateRaycast => false;
bool uGUI_INavigableIconGrid.SelectItemClosestToPosition(Vector3 worldPos) => false;
uGUI_INavigableIconGrid uGUI_INavigableIconGrid.GetNavigableGridInDirection(int dirX, int dirY) => null;
Graphic uGUI_INavigableIconGrid.GetSelectedIcon() => null;
public void SelectItem(object item)
{
DeselectItem();
selectedItem = item as GameObject;
legendChange.SyncLegendBarToGUISelection();
if (!selectedItem)
{
return;
}
if (selectedItem.TryGetComponent(out TMP_InputField selectedInputField))
{
selectedInputField.Select();
}
else // Button
{
selectedItem.transform.GetChild(0).GetComponent<Image>().sprite = MainMenuServerListPanel.SelectedSprite;
selectedItem.GetComponentInChildren<uGUI_BasicColorSwap>().makeTextBlack();
}
if (!EventSystem.current.alreadySelecting)
{
EventSystem.current.SetSelectedGameObject(selectedItem);
}
RuntimeManager.PlayOneShot(MainMenuServerListPanel.HoverSound.path);
}
public void DeselectItem()
{
if (!selectedItem)
{
return;
}
if (selectedItem.TryGetComponent(out TMP_InputField selectedInputField))
{
selectedInputField.DeactivateInputField();
selectedInputField.ReleaseSelection();
}
else // Button
{
selectedItem.transform.GetChild(0).GetComponent<Image>().sprite = MainMenuServerListPanel.NormalSprite;
selectedItem.transform.GetChild(0).GetComponent<uGUI_BasicColorSwap>().makeTextWhite();
}
if (!EventSystem.current.alreadySelecting)
{
EventSystem.current.SetSelectedGameObject(null);
}
selectedItem = null;
}
public void DeselectAllItems()
{
foreach (GameObject child in selectableItems)
{
selectedItem = child;
DeselectItem();
}
}
public bool SelectFirstItem()
{
SelectItem(selectableItems[0]);
return true;
}
public bool SelectItemInDirection(int dirX, int dirY)
{
if (!selectedItem)
{
return SelectFirstItem();
}
if (dirX == dirY)
{
return false;
}
int dir = dirX + dirY > 0 ? 1 : -1;
for (int newIndex = GetSelectedIndex() + dir; newIndex >= 0 && newIndex < selectableItems.Length; newIndex += dir)
{
if (SelectItemByIndex(newIndex))
{
return true;
}
}
return false;
}
private int GetSelectedIndex() => selectedItem ? Array.IndexOf(selectableItems, selectedItem) : -1;
private bool SelectItemByIndex(int selectedIndex)
{
if (selectedIndex >= 0 && selectedIndex < selectableItems.Length)
{
SelectItem(selectableItems[selectedIndex]);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,21 @@
namespace NitroxClient.MonoBehaviours.Gui.MainMenu.ServersList;
public class MainMenuDeleteServer : uGUI_NavigableControlGrid, uGUI_IButtonReceiver
{
public MainMenuServerButton serverButton;
private void Start() => interGridNavigation = new uGUI_InterGridNavigation();
public bool OnButtonDown(GameInput.Button button)
{
if (button != GameInput.Button.UICancel)
{
return false;
}
OnBack();
return true;
}
public void OnBack() => serverButton.CancelDelete();
}

View File

@@ -0,0 +1,220 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using NitroxClient.GameLogic.Settings;
using NitroxClient.MonoBehaviours.Gui.MainMenu.ServerJoin;
using NitroxClient.Unity.Helper;
using NitroxModel.Serialization;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace NitroxClient.MonoBehaviours.Gui.MainMenu.ServersList;
public class MainMenuServerButton : MonoBehaviour
{
private static MainMenuLoadButton loadButtonRef;
private static LegendButtonData[] confirmButtonLegendData;
private static GameObject deleteButtonRef;
private CanvasGroup loadCg;
private CanvasGroup deleteCg;
private Button cancelDeleteButton;
private string joinIp;
private int joinPort;
private string joinServerName;
public static void Setup(MainMenuLoadButton _loadButtonRef)
{
loadButtonRef = _loadButtonRef;
confirmButtonLegendData = _loadButtonRef.GetComponent<mGUI_Change_Legend_On_Select>().legendButtonConfiguration;
deleteButtonRef = _loadButtonRef.deleteButton;
}
public void Init(string serverName, string ip, int port, bool isReadOnly)
{
joinIp = ip;
joinPort = port;
joinServerName = serverName;
Transform loadTransform = this.RequireTransform("Load");
loadCg = loadTransform.gameObject.AddComponent<CanvasGroup>();
Transform newGameButtonTransform = loadTransform.RequireTransform("NewGameButton");
TextMeshProUGUI tmp = newGameButtonTransform.RequireTransform("Text").GetComponent<TextMeshProUGUI>();
Destroy(tmp.GetComponent<TranslationLiveUpdate>());
StringBuilder buttonText = new(Language.main.Get("Nitrox_ConnectTo"));
buttonText.Append(" <b>").Append(serverName).AppendLine("</b>");
if (NitroxPrefs.HideIp.Value)
{
buttonText.AppendLine("***.***.***.***:*****");
}
else
{
buttonText.Append(ip[^Math.Min(ip.Length, 25)..]).Append(':').Append(port);
}
tmp.text = buttonText.ToString();
Button multiplayerJoinButton = newGameButtonTransform.GetComponent<Button>();
multiplayerJoinButton.onClick = new Button.ButtonClickedEvent();
multiplayerJoinButton.onClick.AddListener(() => _ = OnJoinButtonClicked());
gameObject.GetComponent<mGUI_Change_Legend_On_Select>().legendButtonConfiguration = confirmButtonLegendData;
// We don't want servers that are discovered automatically to be deleted
if (isReadOnly)
{
Destroy(transform.Find("Delete").gameObject);
return;
}
GameObject delete = Instantiate(deleteButtonRef, loadTransform, false);
Button deleteButtonButton = delete.GetComponent<Button>();
deleteButtonButton.onClick = new Button.ButtonClickedEvent();
deleteButtonButton.onClick.AddListener(RequestDelete);
Transform deleteTransform = this.RequireTransform("Delete");
Destroy(deleteTransform.GetComponent<MainMenuDeleteGame>());
Destroy(deleteTransform.GetComponent<TranslationLiveUpdate>());
deleteCg = deleteTransform.GetComponent<CanvasGroup>();
cancelDeleteButton = deleteTransform.RequireTransform("DeleteCancelButton").GetComponent<Button>();
cancelDeleteButton.onClick = new Button.ButtonClickedEvent();
cancelDeleteButton.onClick.AddListener(CancelDelete);
Button confirmDeleteButton = deleteTransform.RequireTransform("DeleteConfirmButton").GetComponent<Button>();
confirmDeleteButton.onClick = new Button.ButtonClickedEvent();
confirmDeleteButton.onClick.AddListener(Delete);
deleteTransform.gameObject.AddComponent<MainMenuDeleteServer>().serverButton = this;
TextMeshProUGUI warningTmp = deleteTransform.RequireTransform("DeleteWarningText").GetComponent<TextMeshProUGUI>();
warningTmp.text = Language.main.Get("Nitrox_ServerEntry_DeleteWarning");
}
public void RequestDelete()
{
uGUI_MainMenu.main.OnRightSideOpened(deleteCg.gameObject);
uGUI_LegendBar.ClearButtons();
uGUI_LegendBar.ChangeButton(0, uGUI.FormatButton(GameInput.Button.UICancel, gamePadOnly: true), Language.main.GetFormat("Back"));
uGUI_LegendBar.ChangeButton(1, uGUI.FormatButton(GameInput.Button.UISubmit, gamePadOnly: true), Language.main.GetFormat("ItemSelectorSelect"));
StartCoroutine(loadButtonRef.ShiftAlpha(loadCg, 0.0f, loadButtonRef.animTime, loadButtonRef.alphaPower, false));
StartCoroutine(loadButtonRef.ShiftAlpha(deleteCg, 1f, loadButtonRef.animTime, loadButtonRef.alphaPower, true, cancelDeleteButton));
StartCoroutine(loadButtonRef.ShiftPos(loadCg, MainMenuLoadButton.target.left, MainMenuLoadButton.target.centre, loadButtonRef.animTime, loadButtonRef.posPower));
StartCoroutine(loadButtonRef.ShiftPos(deleteCg, MainMenuLoadButton.target.centre, MainMenuLoadButton.target.right, loadButtonRef.animTime, loadButtonRef.posPower));
}
public void CancelDelete()
{
MainMenuRightSide.main.OpenGroup(MainMenuServerListPanel.NAME);
if (GameInput.IsPrimaryDeviceGamepad())
MainMenuServerListPanel.Main.SelectItemByIndex(MainMenuServerListPanel.Main.GetSelectedIndex());
StartCoroutine(loadButtonRef.ShiftAlpha(loadCg, 1f, loadButtonRef.animTime, loadButtonRef.alphaPower, true));
StartCoroutine(loadButtonRef.ShiftAlpha(deleteCg, 0.0f, loadButtonRef.animTime, loadButtonRef.alphaPower, false));
StartCoroutine(loadButtonRef.ShiftPos(loadCg, MainMenuLoadButton.target.centre, MainMenuLoadButton.target.left, loadButtonRef.animTime, loadButtonRef.posPower));
StartCoroutine(loadButtonRef.ShiftPos(deleteCg, MainMenuLoadButton.target.right, MainMenuLoadButton.target.centre, loadButtonRef.animTime, loadButtonRef.posPower));
}
public void ResetLoadDeleteView()
{
loadCg.alpha = 1;
loadCg.interactable = loadCg.blocksRaycasts = true;
RectTransform loadTransform = loadCg.GetComponent<RectTransform>();
float loadPosX = loadTransform.sizeDelta.x * 0.5f;
loadTransform.localPosition = new Vector3(loadPosX, loadTransform.localPosition.y, 0);
if (deleteCg) // Read only server entries
{
RectTransform deleteTransform = deleteCg.GetComponent<RectTransform>();
float deletePosX = deleteTransform.sizeDelta.x * 0.5f;
deleteTransform.localPosition = new Vector3(deletePosX, deleteTransform.localPosition.y, 0);
deleteCg.alpha = 0;
deleteCg.interactable = deleteCg.blocksRaycasts = false;
}
}
public void Delete()
{
MainMenuRightSide.main.OpenGroup(MainMenuServerListPanel.NAME);
int scrollIndex = MainMenuServerListPanel.Main.GetSelectedIndex();
if (GameInput.IsPrimaryDeviceGamepad() && !MainMenuServerListPanel.Main.SelectItemInYDirection(scrollIndex, 1))
{
MainMenuServerListPanel.Main.SelectItemInYDirection(scrollIndex, -1);
}
StartCoroutine(loadButtonRef.ShiftPos(deleteCg, MainMenuLoadButton.target.left, MainMenuLoadButton.target.centre, loadButtonRef.animTime, loadButtonRef.posPower));
StartCoroutine(loadButtonRef.ShiftAlpha(deleteCg, 0.0f, loadButtonRef.animTime, loadButtonRef.alphaPower, false));
ServerList.Instance.RemoveAt(transform.GetSiblingIndex() - 1);
ServerList.Instance.Save();
Destroy(gameObject);
}
public async Task OnJoinButtonClicked()
{
if (MainMenuServerListPanel.Main.IsJoining)
{
return; // Do not attempt to join multiple servers.
}
MainMenuServerListPanel.Main.IsJoining = true;
MainMenuServerListPanel.Main.DeselectAllItems();
await OpenJoinServerMenuAsync(joinIp, joinPort).ContinueWith(_ => { MainMenuServerListPanel.Main.IsJoining = false; });
MainMenuJoinServerPanel.Instance.UpdatePanelValues(joinServerName);
}
public static async Task OpenJoinServerMenuAsync(string serverIp, int serverPort)
{
if (!MainMenuServerListPanel.Main)
{
Log.Error("MainMenuServerListPanel is not instantiated although OpenJoinServerMenuAsync is called.");
return;
}
IPEndPoint endpoint = ResolveIPEndPoint(serverIp, serverPort);
if (endpoint == null)
{
Log.InGame($"{Language.main.Get("Nitrox_UnableToConnect")}: {serverIp}:{serverPort}");
return;
}
MainMenuNotificationPanel.ShowLoading();
await JoinServerBackend.StartMultiplayerClientAsync(endpoint.Address, endpoint.Port);
}
private static IPEndPoint ResolveIPEndPoint(string serverIp, int serverPort)
{
UriHostNameType hostType = Uri.CheckHostName(serverIp);
IPAddress address;
switch (hostType)
{
case UriHostNameType.IPv4:
case UriHostNameType.IPv6:
IPAddress.TryParse(serverIp, out address);
break;
case UriHostNameType.Dns:
address = ResolveHostName(serverIp, serverPort);
break;
default:
return null;
}
return address != null ? new IPEndPoint(address, serverPort) : null;
static IPAddress ResolveHostName(string hostname, int serverPort)
{
try
{
IPHostEntry hostEntry = Dns.GetHostEntry(hostname);
return hostEntry.AddressList[0];
}
catch (SocketException ex)
{
Log.ErrorSensitive(ex, "Unable to resolve the address {hostname}:{serverPort}", hostname, serverPort);
return null;
}
}
}
}

View File

@@ -0,0 +1,355 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using FMODUnity;
using NitroxClient.Communication;
using NitroxClient.GameLogic.Settings;
using NitroxClient.Unity.Helper;
using NitroxModel;
using NitroxModel.Serialization;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UWE;
namespace NitroxClient.MonoBehaviours.Gui.MainMenu.ServersList;
public class MainMenuServerListPanel : MonoBehaviour, uGUI_INavigableIconGrid, uGUI_IButtonReceiver
{
public const string NAME = "MultiplayerServerList";
public static MainMenuServerListPanel Main;
public static Sprite NormalSprite;
public static Sprite SelectedSprite;
public static FMODAsset HoverSound;
private GameObject multiplayerNewServerButtonRef;
private GameObject multiplayerServerButtonRef;
private Transform serverAreaContent;
private GameObject selectedServerItem;
private ScrollRect scrollRect;
private GameObject scrollBar;
public bool IsJoining { get; set; }
public void Setup(GameObject savedGamesRef)
{
Main = this;
MainMenuLoadMenu loadMenu = savedGamesRef.GetComponentInChildren<MainMenuLoadMenu>();
NormalSprite = loadMenu.normalSprite;
SelectedSprite = loadMenu.selectedSprite;
HoverSound = loadMenu.hoverSound;
multiplayerNewServerButtonRef = savedGamesRef.RequireGameObject("Scroll View/Viewport/SavedGameAreaContent/NewGame");
serverAreaContent = transform.RequireTransform("Scroll View/Viewport/SavedGameAreaContent");
serverAreaContent.gameObject.name = "ServerAreaContent";
serverAreaContent.GetComponent<GridLayoutGroup>().spacing = new Vector2(0, 5);
scrollRect = transform.RequireGameObject("Scroll View").GetComponent<ScrollRect>();
scrollBar = scrollRect.RequireGameObject("Scrollbar");
multiplayerServerButtonRef = savedGamesRef.GetComponent<MainMenuLoadPanel>().saveInstance;
MainMenuServerButton.Setup(multiplayerServerButtonRef.GetComponent<MainMenuLoadButton>());
RefreshServerEntries();
}
public bool OnButtonDown(GameInput.Button button)
{
switch (button)
{
case GameInput.Button.UISubmit:
OnConfirm();
return true;
case GameInput.Button.UICancel:
OnBack();
return true;
case GameInput.Button.UIClear:
OnClear();
return true;
default:
return false;
}
}
public void OnBack()
{
DeselectAllItems();
MainMenuRightSide.main.OpenGroup("Home");
}
public void OnClear()
{
if (selectedServerItem && selectedServerItem.TryGetComponent(out MainMenuServerButton serverButton))
{
serverButton.RequestDelete();
}
}
public void OnConfirm()
{
if (!selectedServerItem)
{
return;
}
if (selectedServerItem.gameObject.name == "NewServer")
{
DeselectAllItems();
MainMenuRightSide.main.OpenGroup(MainMenuCreateServerPanel.NAME);
}
else if (selectedServerItem.TryGetComponent(out MainMenuServerButton serverButton))
{
_ = serverButton.OnJoinButtonClicked().ContinueWithHandleError(Log.Error);
}
}
object uGUI_INavigableIconGrid.GetSelectedItem() => selectedServerItem;
bool uGUI_INavigableIconGrid.ShowSelector => false;
bool uGUI_INavigableIconGrid.EmulateRaycast => false;
bool uGUI_INavigableIconGrid.SelectItemClosestToPosition(Vector3 worldPos) => false;
uGUI_INavigableIconGrid uGUI_INavigableIconGrid.GetNavigableGridInDirection(int dirX, int dirY) => null;
Graphic uGUI_INavigableIconGrid.GetSelectedIcon() => null;
public void SelectItem(object item)
{
DeselectItem();
selectedServerItem = item as GameObject;
if (!selectedServerItem)
{
return;
}
if (selectedServerItem.TryGetComponentInChildren(out mGUI_Change_Legend_On_Select componentInChildren))
{
componentInChildren.SyncLegendBarToGUISelection();
}
if (selectedServerItem == serverAreaContent.GetChild(0).gameObject) // Server Create Button
{
selectedServerItem.transform.Find("NewGameButton").GetComponent<Image>().sprite = SelectedSprite;
}
else
{
selectedServerItem.transform.Find("Load/NewGameButton").GetComponent<Image>().sprite = SelectedSprite;
}
selectedServerItem.GetComponentInChildren<uGUI_BasicColorSwap>();
UIUtils.ScrollToShowItemInCenter(selectedServerItem.transform);
RuntimeManager.PlayOneShot(HoverSound.path);
}
public void DeselectItem()
{
if (!selectedServerItem)
{
return;
}
if (selectedServerItem == serverAreaContent.GetChild(0).gameObject) // Server Create Button
{
selectedServerItem.transform.Find("NewGameButton").GetComponent<Image>().sprite = NormalSprite;
}
else
{
selectedServerItem.transform.Find("Load/NewGameButton").GetComponent<Image>().sprite = NormalSprite;
}
selectedServerItem.GetComponentInChildren<uGUI_BasicColorSwap>().makeTextWhite();
selectedServerItem = null;
}
public void DeselectAllItems()
{
// Create ServerEntry button
serverAreaContent.GetChild(0).Find("NewGameButton").GetComponent<Image>().sprite = NormalSprite;
serverAreaContent.GetChild(0).GetComponentInChildren<uGUI_BasicColorSwap>().makeTextWhite();
// Server buttons
for (int i = 1; i < serverAreaContent.childCount; i++)
{
Transform child = serverAreaContent.GetChild(i);
child.Find("Load/NewGameButton").GetComponent<Image>().sprite = NormalSprite;
child.GetComponentInChildren<uGUI_BasicColorSwap>().makeTextWhite();
child.GetComponent<MainMenuServerButton>().ResetLoadDeleteView();
}
}
public bool SelectFirstItem()
{
MainMenuServerButton firstServerObject = serverAreaContent.GetComponentInChildren<MainMenuServerButton>();
if (firstServerObject)
{
SelectItem(firstServerObject.gameObject);
return true;
}
Transform serverCreationButton = serverAreaContent.GetChild(0);
if (serverCreationButton && serverCreationButton.name == "NewServer")
{
SelectItem(serverCreationButton.gameObject);
return true;
}
return false;
}
public bool SelectItemInDirection(int dirX, int dirY)
{
if (selectedServerItem)
{
return dirY != 0 && SelectItemInYDirection(GetSelectedIndex(), dirY);
}
return SelectFirstItem();
}
public int GetSelectedIndex() => selectedServerItem ? selectedServerItem.transform.GetSiblingIndex() : -1;
public bool SelectItemInYDirection(int selectedIndex, int dirY)
{
int dir = dirY > 0 ? 1 : -1;
for (int newIndex = selectedIndex + dir; newIndex >= 0 && newIndex < serverAreaContent.childCount; newIndex += dir)
{
if (SelectItemByIndex(newIndex))
{
return true;
}
}
return false;
}
public bool SelectItemByIndex(int selectedIndex)
{
if (selectedIndex < serverAreaContent.childCount && selectedIndex >= 0)
{
SelectItem(serverAreaContent.GetChild(selectedIndex).gameObject);
return true;
}
return false;
}
private void LoadSavedServers()
{
ServerList.Refresh();
foreach (ServerList.Entry entry in ServerList.Instance.Entries)
{
CreateServerButton(entry.Name, entry.Address, entry.Port);
}
}
private IEnumerator FindLANServers()
{
void LateAddButton(IPEndPoint serverEndPoint)
{
if (!ServerList.Instance.Entries.Any(e => e.Address == serverEndPoint.Address.ToString() && e.Port == serverEndPoint.Port))
{
Log.Info($"Adding LAN server: {serverEndPoint}");
// Add ServerList entry to keep indices in sync with servers UI, to enable removal by index
ServerList.Instance.Add(new ServerList.Entry("LAN Server", serverEndPoint.Address, serverEndPoint.Port, false));
CreateServerButton("LAN Server", serverEndPoint.Address.ToString(), serverEndPoint.Port, true);
}
}
using Task<IEnumerable<IPEndPoint>> searchTask = LANBroadcastClient.SearchAsync();
while (!searchTask.IsCompleted)
{
while (LANBroadcastClient.DiscoveredServers.TryDequeue(out IPEndPoint endPoint))
{
LateAddButton(endPoint);
}
yield return null;
}
while (LANBroadcastClient.DiscoveredServers.TryDequeue(out IPEndPoint endPoint))
{
LateAddButton(endPoint);
}
ServerList.Instance.Save();
}
public GameObject CreateServerButton(string serverName, string address, int port, bool isReadOnly = false)
{
GameObject multiplayerButtonInst = Instantiate(multiplayerServerButtonRef, serverAreaContent, false);
multiplayerButtonInst.name = $"NitroxServer_{serverAreaContent.childCount - 2}";
DestroyImmediate(multiplayerButtonInst.RequireGameObject("Load")); // Needs to be deleted before MainMenuServerButton.Init() below
Destroy(multiplayerButtonInst.GetComponent<MainMenuLoadButton>());
GameObject multiplayerLoadButtonInst = Instantiate(multiplayerNewServerButtonRef, multiplayerButtonInst.transform, false);
multiplayerLoadButtonInst.name = "Load";
MainMenuServerButton serverButton = multiplayerButtonInst.AddComponent<MainMenuServerButton>();
serverButton.Init(serverName, address, port, isReadOnly);
scrollBar.SetActive(serverAreaContent.childCount >= 4);
foreach (EventTrigger eventTrigger in multiplayerButtonInst.GetComponentsInChildren<EventTrigger>(true))
{
ForwardTriggerScrollToScrollRect(eventTrigger);
}
return multiplayerButtonInst;
}
private void CreateAddServerButton()
{
GameObject multiplayerButtonInst = Instantiate(multiplayerNewServerButtonRef, serverAreaContent, false);
multiplayerButtonInst.name = "NewServer"; // "NewServer" is important, see OnConfirm()
TextMeshProUGUI txt = multiplayerButtonInst.RequireTransform("NewGameButton/Text").GetComponent<TextMeshProUGUI>();
txt.text = "Nitrox_AddServer";
txt.fontSize *= 1.5f;
txt.fontStyle = FontStyles.Bold;
Button multiplayerButtonButton = multiplayerButtonInst.RequireTransform("NewGameButton").GetComponent<Button>();
multiplayerButtonButton.onClick = new Button.ButtonClickedEvent();
multiplayerButtonButton.onClick.AddListener(OpenAddServerGroup);
ForwardTriggerScrollToScrollRect(multiplayerButtonButton.GetComponent<EventTrigger>());
}
private void ForwardTriggerScrollToScrollRect(EventTrigger eventTrigger)
{
eventTrigger.triggers.RemoveAll(trigger => trigger.eventID == EventTriggerType.Scroll);
EventTrigger.TriggerEvent callback = new();
callback.AddListener(x => scrollRect.Scroll(((PointerEventData)x).scrollDelta.y, 5f));
eventTrigger.triggers.Add(new EventTrigger.Entry
{
eventID = EventTriggerType.Scroll,
callback = callback
});
}
public void OpenAddServerGroup()
{
DeselectAllItems();
MainMenuRightSide.main.OpenGroup(MainMenuCreateServerPanel.NAME);
}
public void RefreshServerEntries()
{
if (!serverAreaContent)
{
return;
}
foreach (Transform child in serverAreaContent)
{
Destroy(child.gameObject);
}
CreateAddServerButton();
LoadSavedServers();
CoroutineHost.StartCoroutine(FindLANServers());
}
}