using Barotrauma.Networking; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using Microsoft.Xna.Framework; namespace Barotrauma.Items.Components { readonly struct TerminalMessage { public readonly string Text; public readonly Color Color; public readonly bool IsWelcomeMessage; public TerminalMessage(string text, Color color, bool isWelcomeMessage) { Text = text; Color = color; IsWelcomeMessage = isWelcomeMessage; } public void Deconstruct(out string text, out Color color) { text = Text; color = Color; } } partial class Terminal : ItemComponent { private const int MaxMessageLength = ChatMessage.MaxLength; private const int MaxMessages = 60; private readonly List messageHistory = new List(MaxMessages); public LocalizedString DisplayedWelcomeMessage { get; private set; } private string welcomeMessage; [InGameEditable, Serialize("", IsPropertySaveable.Yes, "Message to be displayed on the terminal display when it is first opened.", translationTextTag: "terminalwelcomemsg.", alwaysUseInstanceValues: true)] public string WelcomeMessage { get { return welcomeMessage; } set { if (welcomeMessage == value) { return; } welcomeMessage = value; DisplayedWelcomeMessage = TextManager.Get(welcomeMessage).Fallback(welcomeMessage.Replace("\\n", "\n")); } } /// /// Can be used to display messages on the terminal via status effects /// public string ShowMessage { get { return messageHistory.Count == 0 ? string.Empty : messageHistory.Last().Text; } set { if (string.IsNullOrEmpty(value)) { return; } ShowOnDisplay(value, addToHistory: true, TextColor, isWelcomeMessage: false); } } [Editable, Serialize(false, IsPropertySaveable.Yes, description: "The terminal will use a monospace font if this box is ticked.", alwaysUseInstanceValues: true)] public bool UseMonospaceFont { get; set; } [Serialize(false, IsPropertySaveable.No)] public bool AutoHideScrollbar { get; set; } [Serialize(false, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)] public bool WelcomeMessageDisplayed { get; set; } private Color textColor = Color.LimeGreen; [Editable, Serialize("50,205,50,255", IsPropertySaveable.Yes, description: "Color of the terminal text.", alwaysUseInstanceValues: true)] public Color TextColor { get => textColor; set { textColor = value; #if CLIENT if (inputBox is { } input) { input.TextColor = value; } #endif } } [Editable, Serialize("> ", IsPropertySaveable.Yes)] public string LineStartSymbol { get; set; } private bool _readonly; [Editable, Serialize(false, IsPropertySaveable.Yes)] public bool Readonly { get => _readonly; set { _readonly = value; #if CLIENT RefreshInputElements(); #endif } } [Serialize(true, IsPropertySaveable.No)] public bool AutoScrollToBottom { get; set; } private string OutputValue { get; set; } private string prevColorSignal; public Terminal(Item item, ContentXElement element) : base(item, element) { IsActive = true; InitProjSpecific(element); } partial void InitProjSpecific(XElement element); partial void ShowOnDisplay(string input, bool addToHistory, Color color, bool isWelcomeMessage); public override void ReceiveSignal(Signal signal, Connection connection) { switch (connection.Name) { case "set_text": case "signal_in": if (string.IsNullOrEmpty(signal.value)) { return; } if (signal.value.Length > MaxMessageLength) { signal.value = signal.value.Substring(0, MaxMessageLength); } string inputSignal = signal.value.Replace("\\n", "\n"); ShowOnDisplay(inputSignal, addToHistory: true, TextColor, isWelcomeMessage: false); break; case "set_text_color": if (signal.value != prevColorSignal) { TextColor = XMLExtensions.ParseColor(signal.value, false); prevColorSignal = signal.value; } break; case "clear_text" when signal.value != "0": messageHistory.Clear(); #if CLIENT if (historyBox?.Content is { } history) { history.ClearChildren(); } CreateFillerBlock(); #endif break; } } public override void OnItemLoaded() { bool isSubEditor = false; #if CLIENT isSubEditor = Screen.Selected == GameMain.SubEditorScreen || GameMain.GameSession?.GameMode is TestGameMode; #endif base.OnItemLoaded(); if (!DisplayedWelcomeMessage.IsNullOrEmpty() && !WelcomeMessageDisplayed) { ShowOnDisplay(DisplayedWelcomeMessage.Value, addToHistory: !isSubEditor, TextColor, isWelcomeMessage: true); DisplayedWelcomeMessage = ""; //disable welcome message if a game session is running so it doesn't reappear on successive rounds if (GameMain.GameSession != null && !isSubEditor) { WelcomeMessageDisplayed = true; } } } public override XElement Save(XElement parentElement) { var componentElement = base.Save(parentElement); for (int i = 0; i < messageHistory.Count; i++) { var msg = messageHistory[i]; componentElement.Add(new XAttribute("msg" + i, msg.Text)); componentElement.Add(new XAttribute("color" + i, msg.Color.ToStringHex())); if (msg.IsWelcomeMessage) { componentElement.Add(new XAttribute("welcomemessage" + i, true)); } } return componentElement; } public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap) { base.Load(componentElement, usePrefabValues, idRemap, isItemSwap); for (int i = 0; i < MaxMessages; i++) { string msg = componentElement.GetAttributeString("msg" + i, null); if (msg is null) { break; } Color color = componentElement.GetAttributeColor("color" + i, TextColor); bool isWelcomeMessage = componentElement.GetAttributeBool("welcomemessage" + i, false); ShowOnDisplay(msg, addToHistory: true, color, isWelcomeMessage); } } } }