diff --git a/QSB/AssetBundles/qsb_hud b/QSB/AssetBundles/qsb_hud index 3233cc3e..bf94b8c7 100644 Binary files a/QSB/AssetBundles/qsb_hud and b/QSB/AssetBundles/qsb_hud differ diff --git a/QSB/AssetBundles/qsb_network b/QSB/AssetBundles/qsb_network index 036ad744..4da4e11c 100644 Binary files a/QSB/AssetBundles/qsb_network and b/QSB/AssetBundles/qsb_network differ diff --git a/QSB/DeathSync/Messages/PlayerDeathMessage.cs b/QSB/DeathSync/Messages/PlayerDeathMessage.cs index 9f2f2b9f..5aedbef6 100644 --- a/QSB/DeathSync/Messages/PlayerDeathMessage.cs +++ b/QSB/DeathSync/Messages/PlayerDeathMessage.cs @@ -1,5 +1,6 @@ using Mirror; using QSB.ClientServerStateSync; +using QSB.HUD; using QSB.Messaging; using QSB.Player; using QSB.RespawnSync; @@ -40,7 +41,7 @@ public class PlayerDeathMessage : QSBMessage var deathMessage = Necronomicon.GetPhrase(Data, NecronomiconIndex); if (deathMessage != null) { - DebugLog.ToAll(string.Format(deathMessage, playerName)); + MultiplayerHUDManager.Instance.WriteMessage($"{string.Format(deathMessage, playerName)}"); } RespawnManager.Instance.OnPlayerDeath(player); diff --git a/QSB/HUD/Messages/ChatMessage.cs b/QSB/HUD/Messages/ChatMessage.cs new file mode 100644 index 00000000..a8404ade --- /dev/null +++ b/QSB/HUD/Messages/ChatMessage.cs @@ -0,0 +1,20 @@ +using QSB.Messaging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QSB.HUD.Messages; + +internal class ChatMessage : QSBMessage +{ + public ChatMessage(string msg) : base(msg) { } + + public override void OnReceiveLocal() => OnReceiveRemote(); + + public override void OnReceiveRemote() + { + MultiplayerHUDManager.Instance.WriteMessage(Data); + } +} \ No newline at end of file diff --git a/QSB/HUD/MultiplayerHUDManager.cs b/QSB/HUD/MultiplayerHUDManager.cs index 09de9cd2..53f5fd34 100644 --- a/QSB/HUD/MultiplayerHUDManager.cs +++ b/QSB/HUD/MultiplayerHUDManager.cs @@ -1,12 +1,16 @@ using QSB.HUD.Messages; +using QSB.Localization; using QSB.Messaging; using QSB.Player; using QSB.ServerSettings; using QSB.Utility; using QSB.WorldSync; +using System; +using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.SceneManagement; +using UnityEngine.UI; namespace QSB.HUD; @@ -15,6 +19,8 @@ internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart public static MultiplayerHUDManager Instance; private Transform _playerList; + private Transform _textChat; + private InputField _inputField; private Material _markerMaterial; public static Sprite UnknownSprite; @@ -59,6 +65,89 @@ internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart SpaceSprite = QSBCore.HUDAssetBundle.LoadAsset("Assets/MULTIPLAYER_UI/playerbox_space.png"); } + private const int LINE_COUNT = 11; + private const int CHAR_COUNT = 41; + + private bool _writingMessage; + private List _lines = new List(new string[LINE_COUNT]); + private ListStack _messages = new(LINE_COUNT); + + public void WriteMessage(string message) + { + /* Tricky problem to solve. + * - 11 available lines for text to fit onto + * - Each line can be max 41 characters + * - Newest messages apepear at the bottom, and get pushed up by newer messages. + * - Messages can use several lines. + * + * From newest to oldest message, work out how many lines it needs + * and set the lines correctly bottom-up. + */ + + _messages.Push(message); + + if (_messages.Count > LINE_COUNT) + { + _messages.RemoveFirstElementAndShift(); + } + + _lines = new List(new string[LINE_COUNT]); + + var currentLineIndex = 10; + + foreach (var item in _messages.Reverse()) + { + var characterCount = item.Length; + var linesNeeded = Mathf.CeilToInt((float)characterCount / CHAR_COUNT); + var chunk = 0; + for (var i = linesNeeded - 1; i >= 0; i--) + { + if (currentLineIndex - i < 0) + { + chunk++; ; + continue; + } + + var chunkString = string.Concat(item.Skip(CHAR_COUNT * chunk).Take(CHAR_COUNT)); + _lines[currentLineIndex - i] = chunkString; + chunk++; + } + + currentLineIndex -= linesNeeded; + + if (currentLineIndex < 0) + { + break; + } + } + + var finalText = ""; + foreach (var line in _lines) + { + if (line == default) + { + finalText += Environment.NewLine; + } + else if (line.Length == 42) + { + finalText += line; + } + else + { + finalText += $"{line}{Environment.NewLine}"; + } + } + + _textChat.Find("Messages").Find("Message").GetComponent().text = finalText; + + if (Locator.GetPlayerSuit().IsWearingHelmet()) + { + var audioController = Locator.GetPlayerAudioController(); + audioController.PlayNotificationTextScrolling(); + Delay.RunFramesLater(10, () => audioController.StopNotificationTextScrolling()); + } + } + private void Update() { if (!QSBWorldSync.AllObjectsReady || _playerList == null) @@ -67,6 +156,34 @@ internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart } _playerList.gameObject.SetActive(ServerSettingsManager.ShowExtraHUD); + + var inSuit = Locator.GetPlayerSuit().IsWearingHelmet(); + + if (OWInput.IsNewlyPressed(InputLibrary.enter, InputMode.Character) && !_writingMessage && inSuit) + { + OWInput.ChangeInputMode(InputMode.KeyboardInput); + _writingMessage = true; + _inputField.ActivateInputField(); + } + + if (OWInput.IsNewlyPressed(InputLibrary.enter, InputMode.KeyboardInput) && _writingMessage) + { + OWInput.RestorePreviousInputs(); + _writingMessage = false; + _inputField.DeactivateInputField(); + + var message = _inputField.text; + _inputField.text = ""; + message = message.Replace("\n", "").Replace("\r", ""); + message = $"{QSBPlayerManager.LocalPlayer.Name}: {message}"; + new ChatMessage(message).Send(); + } + + if (OWInput.IsNewlyPressed(InputLibrary.escape, InputMode.KeyboardInput) && _writingMessage) + { + OWInput.RestorePreviousInputs(); + _writingMessage = false; + } } private void OnWakeUp() @@ -119,6 +236,12 @@ internal class MultiplayerHUDManager : MonoBehaviour, IAddComponentOnStart HUDIconStack.Push(HUDIcon.TIMBER_HEARTH); new PlanetMessage(HUDIcon.TIMBER_HEARTH).Send(); + + _textChat = multiplayerGroup.transform.Find("TextChat"); + var inputFieldGO = _textChat.Find("InputField"); + _inputField = inputFieldGO.GetComponent(); + _inputField.text = ""; + _textChat.Find("Messages").Find("Message").GetComponent().text = ""; } public void UpdateMinimapMarkers(Minimap minimap) diff --git a/QSB/Player/Messages/PlayerJoinMessage.cs b/QSB/Player/Messages/PlayerJoinMessage.cs index e5281c2b..6a8bc110 100644 --- a/QSB/Player/Messages/PlayerJoinMessage.cs +++ b/QSB/Player/Messages/PlayerJoinMessage.cs @@ -1,6 +1,7 @@ using HarmonyLib; using Mirror; using OWML.Common; +using QSB.HUD; using QSB.Localization; using QSB.Messaging; using QSB.Utility; @@ -125,7 +126,7 @@ public class PlayerJoinMessage : QSBMessage var player = QSBPlayerManager.GetPlayer(From); player.Name = PlayerName; - DebugLog.ToAll(string.Format(QSBLocalization.Current.PlayerJoinedTheGame, player.Name), MessageType.Info); + MultiplayerHUDManager.Instance.WriteMessage($"{string.Format(QSBLocalization.Current.PlayerJoinedTheGame, player.Name)}"); DebugLog.DebugWrite($"{player} joined. qsbVersion:{QSBVersion}, gameVersion:{GameVersion}, dlcInstalled:{DlcInstalled}", MessageType.Info); } diff --git a/QSB/Player/Messages/PlayerKickMessage.cs b/QSB/Player/Messages/PlayerKickMessage.cs index 3374e762..7cc62816 100644 --- a/QSB/Player/Messages/PlayerKickMessage.cs +++ b/QSB/Player/Messages/PlayerKickMessage.cs @@ -1,4 +1,5 @@ using Mirror; +using QSB.HUD; using QSB.Localization; using QSB.Menus; using QSB.Messaging; @@ -34,15 +35,15 @@ internal class PlayerKickMessage : QSBMessage { if (QSBPlayerManager.PlayerExists(PlayerId)) { - DebugLog.ToAll(string.Format(QSBLocalization.Current.PlayerWasKicked, QSBPlayerManager.GetPlayer(PlayerId).Name)); + MultiplayerHUDManager.Instance.WriteMessage($"{string.Format(QSBLocalization.Current.PlayerWasKicked, QSBPlayerManager.GetPlayer(PlayerId).Name)}"); return; } - DebugLog.ToAll(string.Format(QSBLocalization.Current.PlayerWasKicked, PlayerId)); + MultiplayerHUDManager.Instance.WriteMessage($"{string.Format(QSBLocalization.Current.PlayerWasKicked, PlayerId)}"); return; } - DebugLog.ToAll(string.Format(QSBLocalization.Current.KickedFromServer, Data)); + MultiplayerHUDManager.Instance.WriteMessage($"{string.Format(QSBLocalization.Current.KickedFromServer, Data)}"); MenuManager.Instance.OnKicked(Data); NetworkClient.Disconnect(); diff --git a/QSB/Utility/ListStack.cs b/QSB/Utility/ListStack.cs index bfc7b81d..89e133a2 100644 --- a/QSB/Utility/ListStack.cs +++ b/QSB/Utility/ListStack.cs @@ -6,7 +6,16 @@ namespace QSB.Utility; public class ListStack : IEnumerable { - private readonly List _items = new(); + private List _items = new(); + + public int Count => _items.Count; + + public ListStack() { } + + public ListStack(int capacity) + { + _items = new List(capacity); + } public void Clear() => _items.Clear(); @@ -33,6 +42,26 @@ public class ListStack : IEnumerable return default; } + public T RemoveFirstElementAndShift() + { + if (_items.Count == 0) + { + return default; + } + + var firstElement = _items[0]; + + if (_items.Count == 0) + { + return firstElement; + } + + // shift list left + _items = _items.GetRange(1, _items.Count - 1); + + return firstElement; + } + public T Peek() => _items.Count > 0 ? _items[_items.Count - 1] : default;