first commit
This commit is contained in:
147
NitroxClient/MonoBehaviours/Gui/Chat/PlayerChat.cs
Normal file
147
NitroxClient/MonoBehaviours/Gui/Chat/PlayerChat.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NitroxClient.GameLogic.ChatUI;
|
||||
using NitroxClient.GameLogic.Settings;
|
||||
using NitroxModel.Core;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Chat
|
||||
{
|
||||
public class PlayerChat : uGUI_InputGroup
|
||||
{
|
||||
private const int LINE_CHAR_LIMIT = 255;
|
||||
private const int MESSAGES_LIMIT = 64;
|
||||
private const float TOGGLED_TRANSPARENCY = 0.4f;
|
||||
public const float CHAT_VISIBILITY_TIME_LENGTH = 6f;
|
||||
|
||||
private static readonly Queue<ChatLogEntry> entries = new Queue<ChatLogEntry>();
|
||||
private Image[] backgroundImages;
|
||||
private CanvasGroup canvasGroup;
|
||||
private InputField inputField;
|
||||
private GameObject logEntryPrefab;
|
||||
|
||||
private PlayerChatManager playerChatManager;
|
||||
private bool transparent;
|
||||
|
||||
public static bool IsReady { get; private set; }
|
||||
|
||||
public string InputText
|
||||
{
|
||||
get => inputField.text;
|
||||
set => inputField.text = value;
|
||||
}
|
||||
|
||||
public IEnumerator SetupChatComponents()
|
||||
{
|
||||
playerChatManager = NitroxServiceLocator.LocateService<PlayerChatManager>();
|
||||
|
||||
canvasGroup = GetComponent<CanvasGroup>();
|
||||
|
||||
logEntryPrefab = GameObject.Find("ChatLogEntryPrefab");
|
||||
logEntryPrefab.AddComponent<PlayerChatLogItem>();
|
||||
logEntryPrefab.SetActive(false);
|
||||
|
||||
GetComponentsInChildren<Button>()[0].onClick.AddListener(ToggleBackgroundTransparency);
|
||||
GetComponentsInChildren<Button>()[1].gameObject.AddComponent<PlayerChatPinButton>();
|
||||
|
||||
inputField = GetComponentInChildren<InputField>();
|
||||
inputField.gameObject.AddComponent<PlayerChatInputField>().InputField = inputField;
|
||||
inputField.GetComponentInChildren<Button>().onClick.AddListener(playerChatManager.SendMessage);
|
||||
|
||||
// We pick any image that's inside the chat component to have all of their opacity lowered
|
||||
backgroundImages = transform.GetComponentsInChildren<Image>();
|
||||
|
||||
yield return new WaitForEndOfFrame(); //Needed so Select() works on initialization
|
||||
IsReady = true;
|
||||
if (NitroxPrefs.SilenceChat.Value)
|
||||
{
|
||||
Log.InGame(Language.main.Get("Nitrox_SilencedChatNotif"));
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteLogEntry(string playerName, string message, Color color)
|
||||
{
|
||||
if (entries.Count == MESSAGES_LIMIT)
|
||||
{
|
||||
Destroy(entries.Dequeue().EntryObject);
|
||||
}
|
||||
|
||||
ChatLogEntry chatLogEntry;
|
||||
GameObject chatLogEntryObject;
|
||||
if (entries.Count != 0 && entries.Last().PlayerName == playerName)
|
||||
{
|
||||
chatLogEntry = entries.Last();
|
||||
chatLogEntry.MessageText += $"{Environment.NewLine}{message}";
|
||||
chatLogEntry.UpdateTime();
|
||||
chatLogEntryObject = chatLogEntry.EntryObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
chatLogEntry = new ChatLogEntry(playerName, SanitizeMessage(message), color);
|
||||
chatLogEntryObject = Instantiate(logEntryPrefab, logEntryPrefab.transform.parent, false);
|
||||
chatLogEntry.EntryObject = chatLogEntryObject;
|
||||
entries.Enqueue(chatLogEntry);
|
||||
}
|
||||
|
||||
chatLogEntryObject.GetComponent<PlayerChatLogItem>().ApplyOnPrefab(chatLogEntry);
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
PlayerChatInputField.ResetTimer();
|
||||
StartCoroutine(ToggleChatFade(true));
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
StartCoroutine(ToggleChatFade(false));
|
||||
}
|
||||
|
||||
public void Select()
|
||||
{
|
||||
base.Select(true);
|
||||
inputField.Select();
|
||||
inputField.ActivateInputField();
|
||||
}
|
||||
|
||||
private static string SanitizeMessage(string message)
|
||||
{
|
||||
message = message.Trim().TrimEnd('\n').Trim();
|
||||
return message.Length < LINE_CHAR_LIMIT ? message : message.Substring(0, LINE_CHAR_LIMIT);
|
||||
}
|
||||
|
||||
private void ToggleBackgroundTransparency()
|
||||
{
|
||||
float alpha = transparent ? 1f : TOGGLED_TRANSPARENCY;
|
||||
transparent = !transparent;
|
||||
|
||||
foreach (Image backgroundImage in backgroundImages)
|
||||
{
|
||||
backgroundImage.CrossFadeAlpha(alpha, 0.5f, false);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator ToggleChatFade(bool fadeIn)
|
||||
{
|
||||
if (fadeIn)
|
||||
{
|
||||
while (canvasGroup.alpha < 1f)
|
||||
{
|
||||
canvasGroup.alpha += 0.01f;
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (canvasGroup.alpha > 0f)
|
||||
{
|
||||
canvasGroup.alpha -= 0.01f;
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
172
NitroxClient/MonoBehaviours/Gui/Chat/PlayerChatInputField.cs
Normal file
172
NitroxClient/MonoBehaviours/Gui/Chat/PlayerChatInputField.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using NitroxClient.GameLogic.ChatUI;
|
||||
using NitroxModel.Core;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Chat
|
||||
{
|
||||
public class PlayerChatInputField : MonoBehaviour, ISelectHandler, IDeselectHandler
|
||||
{
|
||||
private PlayerChatManager playerChatManager;
|
||||
private bool selected;
|
||||
private static float timeLeftUntilAutoClose;
|
||||
public static bool FreezeTime;
|
||||
public InputField InputField;
|
||||
|
||||
// Chat history
|
||||
private const int historyLength = 32; // 2^5 messages availables :D
|
||||
|
||||
private List<string> sentMessages;
|
||||
private int _sentMessagesIndex;
|
||||
private int sentMessagesIndex
|
||||
{
|
||||
get { return _sentMessagesIndex; }
|
||||
set
|
||||
{
|
||||
if (sentMessages.Count == 0)
|
||||
{
|
||||
// -1 is the state when there's no message sent
|
||||
_sentMessagesIndex = -1;
|
||||
}
|
||||
else if (value < 1)
|
||||
{
|
||||
sentMessagesIndex = 1;
|
||||
}
|
||||
else if (value > sentMessages.Count)
|
||||
{
|
||||
_sentMessagesIndex = sentMessages.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// normal functionning
|
||||
InputField.text = sentMessages[value - 1];
|
||||
_sentMessagesIndex = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
playerChatManager = NitroxServiceLocator.LocateService<PlayerChatManager>();
|
||||
sentMessages = new();
|
||||
sentMessagesIndex = -1;
|
||||
}
|
||||
|
||||
public void OnSelect(BaseEventData eventData)
|
||||
{
|
||||
playerChatManager.SelectChat();
|
||||
selected = true;
|
||||
ResetTimer();
|
||||
}
|
||||
|
||||
public void OnDeselect(BaseEventData eventData)
|
||||
{
|
||||
selected = false;
|
||||
}
|
||||
|
||||
public static void ResetTimer()
|
||||
{
|
||||
timeLeftUntilAutoClose = PlayerChat.CHAT_VISIBILITY_TIME_LENGTH;
|
||||
FreezeTime = false;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (FreezeTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(InputField.text))
|
||||
{
|
||||
if (UnityEngine.Input.GetKey(KeyCode.Return))
|
||||
{
|
||||
if (UnityEngine.Input.GetKey(KeyCode.LeftShift))
|
||||
{
|
||||
if (!InputField.text.EndsWith("\n"))
|
||||
{
|
||||
InputField.ActivateInputField();
|
||||
InputField.text += "\n";
|
||||
StartCoroutine(MoveToEndOfText());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Detect if there's a ghost message on top of the list (one that wasn't sent but still saved)
|
||||
if (sentMessagesIndex != sentMessages.Count && sentMessages.Count > 0)
|
||||
{
|
||||
sentMessages.RemoveAt(sentMessages.Count - 1);
|
||||
}
|
||||
|
||||
// If the list is too long, we'll just remove the first message of the list
|
||||
if (sentMessages.Count > historyLength)
|
||||
{
|
||||
sentMessages.RemoveAt(0);
|
||||
}
|
||||
|
||||
sentMessages.Add(InputField.text);
|
||||
_sentMessagesIndex = sentMessages.Count;
|
||||
playerChatManager.SendMessage();
|
||||
playerChatManager.DeselectChat(); // return to game after message sent
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (UnityEngine.Input.GetKey(KeyCode.Return))
|
||||
{
|
||||
ResetTimer();
|
||||
playerChatManager.DeselectChat();
|
||||
}
|
||||
}
|
||||
|
||||
// Chat history stuff
|
||||
// GetKeyDown means it's only getting executed once per press
|
||||
if (UnityEngine.Input.GetKeyDown(KeyCode.UpArrow))
|
||||
{
|
||||
// If we're currently on the newest message, we want to save it to be able to come back to it (a ghost message)
|
||||
if (sentMessagesIndex == sentMessages.Count && sentMessages.Count > 0)
|
||||
{
|
||||
sentMessages.Add(InputField.text);
|
||||
_sentMessagesIndex = sentMessages.Count;
|
||||
}
|
||||
sentMessagesIndex--;
|
||||
}
|
||||
else if(UnityEngine.Input.GetKeyDown(KeyCode.DownArrow))
|
||||
{
|
||||
// We shouldn't execute this check if we're already on top of the list
|
||||
if (sentMessagesIndex < sentMessages.Count)
|
||||
{
|
||||
sentMessagesIndex++;
|
||||
// If we're back to the newest message, we can delete it from the list because it has not been sent yet
|
||||
if (sentMessagesIndex == sentMessages.Count && sentMessages.Count > 0)
|
||||
{
|
||||
sentMessages.RemoveAt(sentMessages.Count - 1);
|
||||
_sentMessagesIndex = sentMessages.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
timeLeftUntilAutoClose -= Time.unscaledDeltaTime;
|
||||
if (timeLeftUntilAutoClose <= 0)
|
||||
{
|
||||
playerChatManager.HideChat();
|
||||
FreezeTime = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator MoveToEndOfText()
|
||||
{
|
||||
yield return null;
|
||||
InputField.MoveTextEnd(false);
|
||||
}
|
||||
}
|
||||
}
|
35
NitroxClient/MonoBehaviours/Gui/Chat/PlayerChatLogItem.cs
Normal file
35
NitroxClient/MonoBehaviours/Gui/Chat/PlayerChatLogItem.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using NitroxClient.GameLogic.ChatUI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Chat
|
||||
{
|
||||
public class PlayerChatLogItem : MonoBehaviour
|
||||
{
|
||||
private Text playerName;
|
||||
private Text time;
|
||||
private Text message;
|
||||
|
||||
private void SetupComponents()
|
||||
{
|
||||
Text[] textFields = gameObject.GetComponentsInChildren<Text>();
|
||||
playerName = textFields[0];
|
||||
time = textFields[1];
|
||||
message = textFields[2];
|
||||
}
|
||||
|
||||
public void ApplyOnPrefab(ChatLogEntry chatLogEntry)
|
||||
{
|
||||
if (playerName == null)
|
||||
{
|
||||
SetupComponents();
|
||||
}
|
||||
|
||||
playerName.text = chatLogEntry.PlayerName;
|
||||
playerName.color = chatLogEntry.PlayerColor;
|
||||
time.text = chatLogEntry.Time;
|
||||
message.text = chatLogEntry.MessageText;
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
66
NitroxClient/MonoBehaviours/Gui/Chat/PlayerChatPinButton.cs
Normal file
66
NitroxClient/MonoBehaviours/Gui/Chat/PlayerChatPinButton.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using NitroxClient.GameLogic.ChatUI;
|
||||
using NitroxModel.Core;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Chat
|
||||
{
|
||||
public class PlayerChatPinButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
|
||||
{
|
||||
private static PlayerChatManager playerChatManager;
|
||||
|
||||
private readonly Camera mainCamera = Camera.main;
|
||||
private Vector2 screenRes = new Vector2(1920f, 1200f);
|
||||
private Vector2 chatSize;
|
||||
private Vector4 screenBorder;
|
||||
private Vector2 offset;
|
||||
private bool drag;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
playerChatManager = NitroxServiceLocator.LocateService<PlayerChatManager>();
|
||||
chatSize = transform.parent.parent.GetComponent<RectTransform>().sizeDelta;
|
||||
}
|
||||
|
||||
public void OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
screenRes.y = (screenRes.x / Screen.width) * Screen.height;
|
||||
offset = GetMouseWorldPosition() - (Vector2)playerChatManager.PlayerChaTransform.localPosition;
|
||||
screenBorder = new Vector4(-(screenRes.x - chatSize.x) / 2f, (screenRes.x - chatSize.x) / 2f, -(screenRes.y - chatSize.y) / 2f, (screenRes.y - chatSize.y) / 2f);
|
||||
|
||||
drag = true;
|
||||
PlayerChatInputField.FreezeTime = true;
|
||||
}
|
||||
|
||||
public void OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
drag = false;
|
||||
PlayerChatInputField.FreezeTime = false;
|
||||
PlayerChatInputField.ResetTimer();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (drag)
|
||||
{
|
||||
playerChatManager.PlayerChaTransform.localPosition = GetChatPosition();
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 GetMouseWorldPosition()
|
||||
{
|
||||
Vector3 position = mainCamera.ScreenToViewportPoint(UnityEngine.Input.mousePosition);
|
||||
position.x = (position.x - 0.5f) * screenRes.x;
|
||||
position.y = (position.y - 0.5f) * screenRes.y;
|
||||
return position;
|
||||
}
|
||||
|
||||
private Vector2 GetChatPosition()
|
||||
{
|
||||
Vector2 position = GetMouseWorldPosition() - offset;
|
||||
position.x = Mathf.Clamp(position.x, screenBorder.x, screenBorder.y);
|
||||
position.y = Mathf.Clamp(position.y, screenBorder.z, screenBorder.w);
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
20
NitroxClient/MonoBehaviours/Gui/HUD/DenyOwnershipHand.cs
Normal file
20
NitroxClient/MonoBehaviours/Gui/HUD/DenyOwnershipHand.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.HUD;
|
||||
|
||||
public class DenyOwnershipHand : MonoBehaviour
|
||||
{
|
||||
private void Start()
|
||||
{
|
||||
// Forces the message to go away after a few seconds.
|
||||
Destroy(this, 2);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
//TODO: Check if this should be Hand
|
||||
HandReticle.main.SetText(HandReticle.TextType.Hand, "Nitrox_DenyOwnershipHand", true);
|
||||
HandReticle.main.SetText(HandReticle.TextType.HandSubscript, string.Empty, false);
|
||||
HandReticle.main.SetIcon(HandReticle.IconType.HandDeny);
|
||||
}
|
||||
}
|
287
NitroxClient/MonoBehaviours/Gui/HUD/RemotePlayerVitals.cs
Normal file
287
NitroxClient/MonoBehaviours/Gui/HUD/RemotePlayerVitals.cs
Normal file
@@ -0,0 +1,287 @@
|
||||
using System;
|
||||
using NitroxClient.GameLogic;
|
||||
using NitroxClient.Unity.Helper;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.HUD;
|
||||
|
||||
public class RemotePlayerVitals : MonoBehaviour
|
||||
{
|
||||
private static readonly Color OXYGEN_BAR_COLOR = new(0.168f, 0.666f, 0.60f, 1.0f);
|
||||
private static readonly Color OXYGEN_BAR_BORDER_COLOR = new(0.227f, 0.949f, 0.969f, 1.0f);
|
||||
private static readonly Color HEALTH_BAR_COLOR = new(0.859f, 0.373f, 0.251f, 1.0f);
|
||||
private static readonly Color HEALTH_BAR_BORDER_COLOR = new(0.824f, 0.651f, 0.424f, 1.0f);
|
||||
private static readonly Color FOOD_BAR_COLOR = new(0.965f, 0.655f, 0.149f, 1.0f);
|
||||
private static readonly Color FOOD_BAR_BORDER_COLOR = new(0.957f, 0.914f, 0.251f, 1.0f);
|
||||
private static readonly Color WATER_BAR_COLOR = new(0.212f, 0.663f, 0.855f, 1.0f);
|
||||
private static readonly Color WATER_BAR_BORDER_COLOR = new(0.227f, 0.949f, 0.969f, 1.0f);
|
||||
private Canvas canvas;
|
||||
private Bar foodBar;
|
||||
private Bar healthBar;
|
||||
private Bar oxygenBar;
|
||||
|
||||
private string playerName;
|
||||
|
||||
private Bar waterBar;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a player vitals UI elements for the player id.
|
||||
/// </summary>
|
||||
/// <param name="playerId">Unique player id to create the vitals UI elements for.</param>
|
||||
public static RemotePlayerVitals CreateForPlayer(RemotePlayer remotePlayer)
|
||||
{
|
||||
RemotePlayerVitals vitals = new GameObject("RemotePlayerVitals").AddComponent<RemotePlayerVitals>();
|
||||
|
||||
try
|
||||
{
|
||||
vitals.canvas = vitals.CreateCanvas(remotePlayer.Body.transform);
|
||||
|
||||
vitals.playerName = remotePlayer.PlayerName;
|
||||
vitals.CreatePlayerName(vitals.canvas);
|
||||
vitals.CreateStats(vitals.canvas);
|
||||
} catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Encountered an error while creating vitals for player {remotePlayer.PlayerId}, destroying them.");
|
||||
Destroy(vitals.gameObject);
|
||||
return null;
|
||||
}
|
||||
|
||||
return vitals;
|
||||
}
|
||||
|
||||
public void SetStatsVisible(bool visible)
|
||||
{
|
||||
oxygenBar.SetVisible(visible);
|
||||
healthBar.SetVisible(visible);
|
||||
foodBar.SetVisible(visible);
|
||||
waterBar.SetVisible(visible);
|
||||
}
|
||||
|
||||
public void SetOxygen(float oxygen, float maxOxygen)
|
||||
{
|
||||
oxygenBar.SetTargetValue(oxygen);
|
||||
oxygenBar.SetMaxValue(maxOxygen);
|
||||
}
|
||||
|
||||
public void SetHealth(float health)
|
||||
{
|
||||
healthBar.SetTargetValue(health);
|
||||
}
|
||||
|
||||
public void SetFood(float food)
|
||||
{
|
||||
foodBar.SetTargetValue(food);
|
||||
}
|
||||
|
||||
public void SetWater(float water)
|
||||
{
|
||||
waterBar.SetTargetValue(water);
|
||||
}
|
||||
|
||||
public void LateUpdate()
|
||||
{
|
||||
oxygenBar.UpdateVisual();
|
||||
healthBar.UpdateVisual();
|
||||
foodBar.UpdateVisual();
|
||||
waterBar.UpdateVisual();
|
||||
|
||||
// Make canvas face camera.
|
||||
Camera camera = Camera.main;
|
||||
if (canvas && camera)
|
||||
{
|
||||
canvas.transform.forward = camera.transform.forward;
|
||||
}
|
||||
}
|
||||
|
||||
private Canvas CreateCanvas(Transform playerTransform)
|
||||
{
|
||||
// Canvas
|
||||
transform.SetParent(playerTransform, false);
|
||||
transform.localPosition = new Vector3(0, 0, 0);
|
||||
|
||||
Canvas vitalsCanvas = gameObject.AddComponent<Canvas>();
|
||||
vitalsCanvas.renderMode = RenderMode.WorldSpace;
|
||||
|
||||
CanvasScaler scaler = gameObject.AddComponent<CanvasScaler>();
|
||||
scaler.dynamicPixelsPerUnit = 100;
|
||||
|
||||
return vitalsCanvas;
|
||||
}
|
||||
|
||||
private void CreateStats(Canvas canvas)
|
||||
{
|
||||
// uGUI is a script at the topmost of the uGUI(Clone) object which contains the uGUI_ classes whe're looking for
|
||||
uGUI uGUI = uGUI.main;
|
||||
if (!uGUI)
|
||||
{
|
||||
throw new NullReferenceException($"[{nameof(RemotePlayerVitals)}] Couldn't find uGUI main instance when creating vitals");
|
||||
}
|
||||
healthBar = CreateBar(uGUI.GetComponentInChildren<uGUI_HealthBar>(true), canvas);
|
||||
oxygenBar = CreateBar(uGUI.GetComponentInChildren<uGUI_OxygenBar>(true), canvas);
|
||||
foodBar = CreateBar(uGUI.GetComponentInChildren<uGUI_FoodBar>(true), canvas);
|
||||
waterBar = CreateBar(uGUI.GetComponentInChildren<uGUI_WaterBar>(true), canvas);
|
||||
}
|
||||
|
||||
private Bar CreateBar<T>(T barBehaviour, Canvas canvas) where T : MonoBehaviour
|
||||
{
|
||||
GameObject originalBar = barBehaviour.gameObject;
|
||||
GameObject cloned = Instantiate(originalBar, canvas.transform, true);
|
||||
|
||||
uGUI_CircularBar newBar = cloned.GetComponentInChildren<uGUI_CircularBar>(true);
|
||||
newBar.texture = originalBar.GetComponentInChildren<uGUI_CircularBar>(true).texture;
|
||||
newBar.overlay = originalBar.GetComponentInChildren<uGUI_CircularBar>(true).overlay;
|
||||
|
||||
cloned.transform.localRotation = Quaternion.identity;
|
||||
// From uGUI_OxygenBar.Awake
|
||||
if (cloned.TryGetComponentInChildren(out TextMeshProUGUI text, true))
|
||||
{
|
||||
text.enableCulling = true;
|
||||
}
|
||||
|
||||
switch (barBehaviour)
|
||||
{
|
||||
case uGUI_HealthBar:
|
||||
newBar.color = HEALTH_BAR_COLOR;
|
||||
newBar.borderColor = HEALTH_BAR_BORDER_COLOR;
|
||||
cloned.transform.localPosition = new Vector3(-0.05f, 0.33f, 0f);
|
||||
cloned.transform.localScale = new Vector3(0.0014f, 0.0014f, 1f);
|
||||
break;
|
||||
case uGUI_OxygenBar:
|
||||
newBar.color = OXYGEN_BAR_COLOR;
|
||||
newBar.borderColor = OXYGEN_BAR_BORDER_COLOR;
|
||||
cloned.transform.localPosition = new Vector3(0.05f, 0.33f, 0f);
|
||||
cloned.transform.localScale = new Vector3(0.0006f, 0.0006f, 1f);
|
||||
// PulseWave is only present for uGUI_OxygenBar
|
||||
Destroy(cloned.FindChild("PulseWave"));
|
||||
break;
|
||||
case uGUI_FoodBar:
|
||||
newBar.color = FOOD_BAR_COLOR;
|
||||
newBar.borderColor = FOOD_BAR_BORDER_COLOR;
|
||||
cloned.transform.localPosition = new Vector3(-0.05f, 0.255f, 0f);
|
||||
cloned.transform.localScale = new Vector3(0.0014f, 0.0014f, 1f);
|
||||
break;
|
||||
case uGUI_WaterBar:
|
||||
newBar.color = WATER_BAR_COLOR;
|
||||
newBar.borderColor = WATER_BAR_BORDER_COLOR;
|
||||
cloned.transform.localPosition = new Vector3(0.05f, 0.255f, 0f);
|
||||
cloned.transform.localScale = new Vector3(0.0014f, 0.0014f, 1f);
|
||||
break;
|
||||
default:
|
||||
Log.Info($"Unhandled bar type: {barBehaviour.GetType()}");
|
||||
break;
|
||||
}
|
||||
|
||||
Destroy(cloned.FindChild("PulseHalo"));
|
||||
Destroy(cloned.GetComponent<T>());
|
||||
cloned.SetActive(true);
|
||||
return new Bar(cloned, 100f, 100f, 0.1f);
|
||||
}
|
||||
|
||||
private void CreatePlayerName(Canvas canvas)
|
||||
{
|
||||
// Text
|
||||
GameObject nameObject = new("RemotePlayerName");
|
||||
nameObject.transform.parent = canvas.transform;
|
||||
|
||||
Text nameText = nameObject.AddComponent<Text>();
|
||||
nameText.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
nameText.text = playerName;
|
||||
Transform nameTransform = nameText.transform;
|
||||
nameTransform.localScale = new Vector3(0.015f, 0.015f, 1f);
|
||||
nameTransform.rotation = canvas.transform.rotation;
|
||||
nameText.fontSize = 14;
|
||||
nameText.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
// Text position
|
||||
RectTransform namePosition = nameObject.GetComponent<RectTransform>();
|
||||
namePosition.localPosition = new Vector3(0, 0.4f, 0);
|
||||
namePosition.sizeDelta = new Vector2(200, 100);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Must stay optional in case the destroy originates from a broken object
|
||||
oxygenBar?.Dispose();
|
||||
healthBar?.Dispose();
|
||||
foodBar?.Dispose();
|
||||
waterBar?.Dispose();
|
||||
}
|
||||
|
||||
private class Bar : IDisposable
|
||||
{
|
||||
private readonly GameObject gameObject;
|
||||
private readonly uGUI_CircularBar circularBar;
|
||||
private readonly TextMeshProUGUI text;
|
||||
|
||||
private bool isDisposed;
|
||||
private float vel;
|
||||
private float current;
|
||||
private float target;
|
||||
private float maximum;
|
||||
private float smoothTime;
|
||||
|
||||
public Bar(GameObject gameObject, float current, float maximum, float smoothTime)
|
||||
{
|
||||
this.gameObject = gameObject;
|
||||
this.current = current;
|
||||
target = current;
|
||||
this.maximum = maximum;
|
||||
this.smoothTime = smoothTime;
|
||||
|
||||
circularBar = gameObject.GetComponentInChildren<uGUI_CircularBar>(true);
|
||||
// text can be null
|
||||
text = gameObject.GetComponentInChildren<TextMeshProUGUI>(true);
|
||||
}
|
||||
|
||||
public void SetTargetValue(float value)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
target = value;
|
||||
}
|
||||
|
||||
public void SetMaxValue(float maxValue)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
maximum = maxValue;
|
||||
}
|
||||
|
||||
public void UpdateVisual()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
// Adapted from uGUI_OxygenBar
|
||||
float percentage = Mathf.Clamp01(target / maximum);
|
||||
current = Mathf.SmoothDamp(current, percentage, ref vel, smoothTime);
|
||||
circularBar.value = current;
|
||||
if (text)
|
||||
{
|
||||
text.SetText(IntStringCache.GetStringForInt(Mathf.RoundToInt(target)));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVisible(bool visible)
|
||||
{
|
||||
gameObject.SetActive(visible);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
isDisposed = true;
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (isDisposed)
|
||||
{
|
||||
throw new ObjectDisposedException("Tried to update visual on a disposed player stat.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
using NitroxModel.Helper;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.XR;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.MainMenu;
|
||||
|
||||
public static class LoadingScreenVersionText
|
||||
{
|
||||
private static GameObject buildWatermark => uGUI.main.overlays.transform.parent.GetComponentInChildren<uGUI_BuildWatermark>().gameObject;
|
||||
|
||||
private static uGUI_TextFade loadingScreenWarning;
|
||||
private static uGUI_TextFade versionText;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
versionText = AddTextToLoadingScreen("LoadingScreenVersionText", $"\nNitrox {NitroxEnvironment.ReleasePhase} V{NitroxEnvironment.Version}");
|
||||
loadingScreenWarning = AddTextToLoadingScreen("LoadingScreenWarnText", $"\n\n{Language.main.Get("Nitrox_LoadingScreenWarn")}");
|
||||
}
|
||||
|
||||
private static uGUI_TextFade AddTextToLoadingScreen(string name, string text)
|
||||
{
|
||||
GameObject gameObject = Object.Instantiate(buildWatermark, buildWatermark.transform.parent);
|
||||
gameObject.name = name;
|
||||
Object.Destroy(gameObject.GetComponent<uGUI_BuildWatermark>());
|
||||
|
||||
uGUI_TextFade textFade = gameObject.AddComponent<uGUI_TextFade>();
|
||||
textFade.SetAlignment(TextAlignmentOptions.TopRight);
|
||||
textFade.SetColor(Color.white.WithAlpha(0.5f));
|
||||
textFade.SetText(text);
|
||||
textFade.FadeIn(1f, null);
|
||||
|
||||
return textFade;
|
||||
}
|
||||
|
||||
public static void DisableWarningText()
|
||||
{
|
||||
loadingScreenWarning.FadeOut(1f, null);
|
||||
if (XRSettings.enabled)
|
||||
{
|
||||
versionText.FadeOut(1f, null);
|
||||
}
|
||||
}
|
||||
}
|
40
NitroxClient/MonoBehaviours/Gui/Input/KeyBindingManager.cs
Normal file
40
NitroxClient/MonoBehaviours/Gui/Input/KeyBindingManager.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NitroxClient.MonoBehaviours.Gui.Input.KeyBindings;
|
||||
using NitroxClient.MonoBehaviours.Gui.Input.KeyBindings.Actions;
|
||||
using NitroxClient.Serialization;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Input
|
||||
{
|
||||
public class KeyBindingManager
|
||||
{
|
||||
public List<KeyBinding> KeyboardKeyBindings { get; }
|
||||
|
||||
public KeyBindingManager()
|
||||
{
|
||||
ClientConfig cfg = ClientConfig.Load(NitroxUser.AppDataPath);
|
||||
KeyboardKeyBindings = new List<KeyBinding>
|
||||
{
|
||||
// new bindings should not be set to a value equivalent to a pre-existing GameInput.Button enum or another custom binding
|
||||
new((int)KeyBindingValues.CHAT, "Chat", GameInput.Device.Keyboard, new ChatKeyBindingAction(), cfg.OpenChatKeybindPrimary, cfg.OpenChatKeybindSecondary),
|
||||
new((int)KeyBindingValues.FOCUS_DISCORD, "Focus Discord (Alt +)", GameInput.Device.Keyboard, new DiscordFocusBindingAction(), cfg.FocusDiscordKeybindPrimary, cfg.FocusDiscordKeybindSecondary),
|
||||
};
|
||||
}
|
||||
|
||||
// Returns highest custom key binding value. If no custom key bindings, returns 0.
|
||||
public int GetHighestKeyBindingValue()
|
||||
{
|
||||
return KeyboardKeyBindings.Select(keyBinding => (int)keyBinding.Button).DefaultIfEmpty(0).Max();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refers the keybinding values used for button creation in the options menu, to a more clear form
|
||||
/// </summary>
|
||||
public enum KeyBindingValues
|
||||
{
|
||||
CHAT = 45,
|
||||
FOCUS_DISCORD = 46
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
using NitroxClient.GameLogic.ChatUI;
|
||||
using NitroxModel.Core;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Input.KeyBindings.Actions
|
||||
{
|
||||
public class ChatKeyBindingAction : KeyBindingAction
|
||||
{
|
||||
public override void Execute()
|
||||
{
|
||||
// If no other UWE input field is currently active then allow chat to open.
|
||||
if (FPSInputModule.current.lastGroup == null)
|
||||
{
|
||||
NitroxServiceLocator.LocateService<PlayerChatManager>().SelectChat();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using NitroxClient.MonoBehaviours.Discord;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Input.KeyBindings.Actions;
|
||||
|
||||
public class DiscordFocusBindingAction : KeyBindingAction
|
||||
{
|
||||
public override void Execute()
|
||||
{
|
||||
if (UnityEngine.Input.GetKey(KeyCode.LeftAlt))
|
||||
{
|
||||
DiscordJoinRequestGui.Select();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Input.KeyBindings.Actions
|
||||
{
|
||||
public abstract class KeyBindingAction
|
||||
{
|
||||
public abstract void Execute();
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Input.KeyBindings
|
||||
{
|
||||
public class DefaultKeyBinding
|
||||
{
|
||||
public string Binding { get; }
|
||||
public GameInput.BindingSet BindingSet { get; }
|
||||
|
||||
public DefaultKeyBinding(string defaultBinding, GameInput.BindingSet defaultBindingSet)
|
||||
{
|
||||
Binding = defaultBinding;
|
||||
BindingSet = defaultBindingSet;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using NitroxClient.MonoBehaviours.Gui.Input.KeyBindings.Actions;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Input.KeyBindings;
|
||||
|
||||
public class KeyBinding
|
||||
{
|
||||
public GameInput.Button Button { get; }
|
||||
public GameInput.Device Device { get; }
|
||||
public string Label { get; }
|
||||
public string PrimaryKey { get; }
|
||||
public string SecondaryKey { get; }
|
||||
public KeyBindingAction Action { get; }
|
||||
|
||||
public KeyBinding(int keyBindingValue, string buttonLabel, GameInput.Device buttonDevice, KeyBindingAction buttonAction, string primaryKey, string secondaryKey = null)
|
||||
{
|
||||
Validate.NotNull(primaryKey);
|
||||
|
||||
Button = (GameInput.Button)keyBindingValue;
|
||||
Device = buttonDevice;
|
||||
Label = buttonLabel;
|
||||
Action = buttonAction;
|
||||
PrimaryKey = primaryKey;
|
||||
SecondaryKey = secondaryKey;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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));
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
36
NitroxClient/MonoBehaviours/Gui/Modals/ConfirmModal.cs
Normal file
36
NitroxClient/MonoBehaviours/Gui/Modals/ConfirmModal.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Modals;
|
||||
|
||||
public class ConfirmModal : Modal
|
||||
{
|
||||
private Action yesCallback;
|
||||
|
||||
public ConfirmModal() : base(yesButtonText: "Confirm", hideNoButton: false, noButtonText: "Cancel", isAvoidable: true, transparency: 0.93f)
|
||||
{ }
|
||||
|
||||
public void Show(string actionText, Action yesCallback)
|
||||
{
|
||||
ModalText = actionText;
|
||||
this.yesCallback = yesCallback;
|
||||
Show();
|
||||
}
|
||||
|
||||
public override void ClickYes()
|
||||
{
|
||||
yesCallback?.Invoke();
|
||||
Hide();
|
||||
OnDeselect();
|
||||
}
|
||||
|
||||
public override void ClickNo()
|
||||
{
|
||||
Hide();
|
||||
OnDeselect();
|
||||
}
|
||||
|
||||
public override void OnDeselect()
|
||||
{
|
||||
yesCallback = null;
|
||||
}
|
||||
}
|
27
NitroxClient/MonoBehaviours/Gui/Modals/InfoModal.cs
Normal file
27
NitroxClient/MonoBehaviours/Gui/Modals/InfoModal.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Modals;
|
||||
|
||||
public class InfoModal : Modal
|
||||
{
|
||||
public InfoModal() : base(yesButtonText: "Ok", isAvoidable: false, transparency: 0.93f, height: 400f)
|
||||
{ }
|
||||
|
||||
public void Show(string actionText)
|
||||
{
|
||||
ModalText = actionText;
|
||||
Show();
|
||||
}
|
||||
|
||||
public override void ClickYes()
|
||||
{
|
||||
Hide();
|
||||
OnDeselect();
|
||||
}
|
||||
|
||||
public IEnumerator ShowAsync(string actionText)
|
||||
{
|
||||
ModalText = actionText;
|
||||
yield return ShowAsync();
|
||||
}
|
||||
}
|
20
NitroxClient/MonoBehaviours/Gui/Modals/KickedModal.cs
Normal file
20
NitroxClient/MonoBehaviours/Gui/Modals/KickedModal.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Modals;
|
||||
|
||||
public class KickedModal : Modal
|
||||
{
|
||||
// When disconnected from the server, we don't want to keep playing
|
||||
public KickedModal() : base(yesButtonText: "OK", freezeGame: true, transparency: 1.0f)
|
||||
{
|
||||
}
|
||||
|
||||
public void Show(string reason)
|
||||
{
|
||||
ModalText = reason;
|
||||
Show();
|
||||
}
|
||||
|
||||
public override void ClickYes()
|
||||
{
|
||||
IngameMenu.main.QuitGame(false);
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Modals;
|
||||
|
||||
/// <summary>
|
||||
/// Extends the IngameMenu with a disconnect popup.
|
||||
/// </summary>
|
||||
public class LostConnectionModal : Modal
|
||||
{
|
||||
public LostConnectionModal() : base(yesButtonText: "OK", modalText: Language.main.Get("Nitrox_LostConnection"), freezeGame: true, transparency: 1.0f)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ClickYes()
|
||||
{
|
||||
IngameMenu.main.QuitGame(false);
|
||||
}
|
||||
}
|
218
NitroxClient/MonoBehaviours/Gui/Modals/Modal.cs
Normal file
218
NitroxClient/MonoBehaviours/Gui/Modals/Modal.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UWE;
|
||||
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Modals;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for Modal components, which are dialog boxes that appear in the middle of the screen
|
||||
/// </summary>
|
||||
public abstract class Modal
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a Modal by its type at any time (static)
|
||||
/// </summary>
|
||||
public static Dictionary<Type, Modal> Modals = new();
|
||||
/// <summary>
|
||||
/// Current modal that is visible on the screen
|
||||
/// </summary>
|
||||
public static Modal CurrentModal;
|
||||
|
||||
private GameObject modalSubWindow;
|
||||
private TextMeshProUGUI text;
|
||||
|
||||
// All the properties that will be overriden by new instances that inherit this class
|
||||
public string SubWindowName { get; init; }
|
||||
public string ModalText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Makes it possible to dismiss the modal by clicking outside of the modal or pressing escape (default false).
|
||||
/// </summary>
|
||||
public bool IsAvoidable { get; init; }
|
||||
public bool HideNoButton { get; init; }
|
||||
|
||||
public string YesButtonText { get; init; }
|
||||
public string NoButtonText { get; init; }
|
||||
|
||||
public bool FreezeGame { get; init; }
|
||||
|
||||
public float Transparency { get; init; }
|
||||
|
||||
public float Height { get; init; }
|
||||
|
||||
// Is useful for calling IngameMenu::OnDeselect() from a modal class (in Hide() for example)
|
||||
public bool IsAvoidableBypass = false;
|
||||
|
||||
public Modal(string yesButtonText = "YES", bool hideNoButton = true, string noButtonText = "NO", string modalText = "", bool isAvoidable = false, bool freezeGame = false, float transparency = 0.392f, float height = 195f)
|
||||
{
|
||||
Type type = GetType();
|
||||
if (Modals.ContainsKey(type))
|
||||
{
|
||||
throw new NotSupportedException($"You cannot set two modals to have the same Type");
|
||||
}
|
||||
|
||||
SubWindowName = GetType().Name;
|
||||
YesButtonText = yesButtonText;
|
||||
HideNoButton = hideNoButton;
|
||||
NoButtonText = noButtonText;
|
||||
ModalText = modalText;
|
||||
IsAvoidable = isAvoidable;
|
||||
FreezeGame = freezeGame;
|
||||
Transparency = transparency; // 0.392 is the default transparency for Subnautica's modal
|
||||
Height = height;
|
||||
|
||||
Log.Debug($"Registered Modal {SubWindowName} of type {type}");
|
||||
Modals.Add(type, this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the Modal to the screen
|
||||
/// </summary>
|
||||
public void Show()
|
||||
{
|
||||
CoroutineHost.StartCoroutine(ShowAsync());
|
||||
}
|
||||
|
||||
public IEnumerator ShowAsync()
|
||||
{
|
||||
CurrentModal?.Hide();
|
||||
CurrentModal = this;
|
||||
yield return ShowImplementation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the Modal from the screen
|
||||
/// </summary>
|
||||
public void Hide()
|
||||
{
|
||||
CurrentModal = null;
|
||||
if (FreezeGame)
|
||||
{
|
||||
FreezeTime.End(FreezeTime.Id.Quit);
|
||||
}
|
||||
if (IsAvoidable)
|
||||
{
|
||||
IngameMenu.main.OnDeselect();
|
||||
}
|
||||
else
|
||||
{
|
||||
IsAvoidableBypass = true;
|
||||
IngameMenu.main.OnDeselect();
|
||||
IsAvoidableBypass = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when this modal is deselected (only when pressing outside of the modal)
|
||||
/// </summary>
|
||||
public virtual void OnDeselect() { }
|
||||
|
||||
/// <summary>
|
||||
/// This creates the modal when showing it for the first time, you can't modify it afterwards
|
||||
/// </summary>
|
||||
private void InitSubWindow()
|
||||
{
|
||||
if (!IngameMenu.main)
|
||||
{
|
||||
throw new NotSupportedException($"Cannot show ingame subwindow {SubWindowName} because the ingame window does not exist.");
|
||||
}
|
||||
|
||||
if (!modalSubWindow)
|
||||
{
|
||||
GameObject derivedSubWindow = IngameMenu.main.transform.Find("QuitConfirmation").gameObject;
|
||||
modalSubWindow = UnityEngine.Object.Instantiate(derivedSubWindow, IngameMenu.main.transform, false);
|
||||
modalSubWindow.name = SubWindowName;
|
||||
|
||||
// Styling.
|
||||
RectTransform main = modalSubWindow.GetComponent<RectTransform>();
|
||||
main.sizeDelta = new Vector2(700, Height);
|
||||
|
||||
RectTransform messageTransform = modalSubWindow.FindChild("Header").GetComponent<RectTransform>();
|
||||
messageTransform.sizeDelta = new Vector2(700, Height);
|
||||
messageTransform.anchoredPosition = new Vector2(0, 50 - Height / 2);
|
||||
}
|
||||
|
||||
modalSubWindow.GetComponent<Image>().color = Color.white.WithAlpha(Transparency);
|
||||
|
||||
// Will happen either it's initialized or not
|
||||
UpdateModal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the modal with informations that may change from one Show() to another
|
||||
/// </summary>
|
||||
private void UpdateModal()
|
||||
{
|
||||
text = modalSubWindow.FindChild("Header").GetComponent<TextMeshProUGUI>();
|
||||
text.text = ModalText;
|
||||
|
||||
GameObject buttonYesObject = modalSubWindow.FindChild("ButtonYes");
|
||||
GameObject buttonNoObject = modalSubWindow.FindChild("ButtonNo");
|
||||
Button yesButton = buttonYesObject.GetComponent<Button>();
|
||||
|
||||
// We need to reinitialize onClick to avoid keeping Persisted Events (which are set manually inside Unity's Editor)
|
||||
yesButton.onClick = new Button.ButtonClickedEvent();
|
||||
yesButton.onClick.AddListener(ClickYes);
|
||||
buttonYesObject.GetComponentInChildren<TextMeshProUGUI>().text = YesButtonText;
|
||||
RectTransform yesButtonTransform = buttonYesObject.GetComponent<RectTransform>();
|
||||
yesButtonTransform.anchoredPosition = new Vector2(yesButtonTransform.anchoredPosition.x, 50f - Height);
|
||||
|
||||
|
||||
// TODO: fix yes and no button positions
|
||||
if (HideNoButton)
|
||||
{
|
||||
UnityEngine.Object.Destroy(buttonNoObject);
|
||||
buttonYesObject.transform.position = new Vector3(modalSubWindow.transform.position.x / 2, buttonYesObject.transform.position.y, buttonYesObject.transform.position.z); // Center Button
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonNoObject)
|
||||
{
|
||||
Button noButton = buttonNoObject.GetComponent<Button>();
|
||||
noButton.onClick = new Button.ButtonClickedEvent();
|
||||
noButton.onClick.AddListener(ClickNo);
|
||||
buttonNoObject.GetComponentInChildren<TextMeshProUGUI>().text = NoButtonText;
|
||||
RectTransform noButtonTransform = buttonNoObject.GetComponent<RectTransform>();
|
||||
noButtonTransform.anchoredPosition = new Vector2(noButtonTransform.anchoredPosition.x, 50f - Height);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClickYes() { }
|
||||
public virtual void ClickNo() { }
|
||||
|
||||
private IEnumerator ShowImplementation()
|
||||
{
|
||||
// Execute frame-by-frame to allow UI scripts to initialize.
|
||||
InitSubWindow();
|
||||
yield return new WaitForEndOfFrame();
|
||||
// Equivalent of IngameMenu.main.Open() but without minding for the freeze
|
||||
IngameMenu.main.gameObject.SetActive(true);
|
||||
IngameMenu.main.Select();
|
||||
yield return new WaitForEndOfFrame();
|
||||
IngameMenu.main.ChangeSubscreen(SubWindowName);
|
||||
yield return new WaitForEndOfFrame();
|
||||
if (FreezeGame)
|
||||
{
|
||||
FreezeTime.Begin(FreezeTime.Id.Quit);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lets you get any existing Modal by its Type
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the modal to get</typeparam>
|
||||
/// <returns>An existing instance of the modal if it already exists, else, a new one</returns>
|
||||
public static T Get<T>() where T : Modal
|
||||
{
|
||||
if (Modals.TryGetValue(typeof(T), out Modal modal))
|
||||
{
|
||||
return (T)modal;
|
||||
}
|
||||
// No need to add entry in dictionary as it's done in constructor
|
||||
return (T)Activator.CreateInstance(typeof(T));
|
||||
}
|
||||
}
|
13
NitroxClient/MonoBehaviours/Gui/Modals/ServerStoppedModal.cs
Normal file
13
NitroxClient/MonoBehaviours/Gui/Modals/ServerStoppedModal.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace NitroxClient.MonoBehaviours.Gui.Modals;
|
||||
|
||||
public class ServerStoppedModal : Modal
|
||||
{
|
||||
public ServerStoppedModal() : base(yesButtonText: "OK", modalText: Language.main.Get("Nitrox_ServerStopped"), freezeGame: true, transparency: 1.0f)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ClickYes()
|
||||
{
|
||||
IngameMenu.main.QuitGame(false);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user