Client LuaCsForBarotrauma
ChatBox.cs
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 
9 namespace Barotrauma
10 {
11  class ChatBox
12  {
13  public const string RadioChatString = "r; ";
14 
15  private readonly GUIListBox chatBox;
16  private Point screenResolution;
17 
18  public readonly ChatManager ChatManager = new ChatManager();
19 
20  public bool IsSinglePlayer { get; private set; }
21 
22  private bool _toggleOpen = true;
23  public bool ToggleOpen
24  {
25  get { return _toggleOpen; }
26  set
27  {
28  _toggleOpen = PreferChatBoxOpen = value;
29  if (value) { hideableElements.Visible = true; }
30  }
31  }
32  private float openState;
33 
34  public static bool PreferChatBoxOpen = true;
35 
36  public bool CloseAfterMessageSent;
37 
38  private float prevUIScale;
39 
40  private readonly GUIFrame channelSettingsFrame;
41  private readonly GUITextBlock radioJammedWarning;
42  private readonly GUITextBox channelText;
43  private readonly GUILayoutGroup channelPickerContent;
44  private readonly GUIButton memButton;
45  private WifiComponent prevRadio;
46 
47  private bool channelMemPending;
48 
49  //individual message texts that pop up when the chatbox is hidden
50  const float PopupMessageDuration = 5.0f;
51  private readonly List<GUIComponent> popupMessages = new List<GUIComponent>();
52 
54  {
55  get { return InputBox.OnEnterPressed; }
56  set { InputBox.OnEnterPressed = value; }
57  }
58 
59  public GUIFrame GUIFrame { get; private set; }
60 
61  public GUITextBox InputBox { get; private set; }
62 
63  private GUIButton toggleButton;
64 
66  {
67  get => toggleButton;
68  set
69  {
70  if (toggleButton != null)
71  {
72  toggleButton.RectTransform.Parent = null;
73  }
74  toggleButton = value;
75  }
76  }
77 
78  private readonly GUIButton showNewMessagesButton;
79 
80  private readonly GUIFrame hideableElements;
81 
82  public const int ToggleButtonWidthRaw = 30;
83  private int popupMessageOffset;
84 
85  private GUIDropDown ChatModeDropDown { get; set; }
86 
87  public ChatBox(GUIComponent parent, bool isSinglePlayer)
88  {
89  this.IsSinglePlayer = isSinglePlayer;
90 
91  screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
92 
93  int toggleButtonWidth = (int)(ToggleButtonWidthRaw * GUI.Scale);
94  GUIFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ChatBoxArea, parent.RectTransform), style: null);
95 
96  hideableElements = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: null);
97 
98  var chatBoxHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.875f), hideableElements.RectTransform), style: "ChatBox");
99  chatBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), chatBoxHolder.RectTransform, Anchor.CenterRight), style: null);
100 
101  // channel settings -----------------------------------------------------------------------------
102 
103  channelSettingsFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), chatBoxHolder.RectTransform, Anchor.TopCenter, Pivot.BottomCenter) { MinSize = new Point(0, 25) }, style: "GUIFrameBottom");
104  var channelSettingsContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), channelSettingsFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
105  {
106  Stretch = true,
107  CanBeFocused = true,
108  RelativeSpacing = 0.01f
109  };
110 
111  radioJammedWarning = new GUITextBlock(new RectTransform(Vector2.One, channelSettingsFrame.RectTransform), TextManager.Get("radiojammedwarning"),
112  textColor: GUIStyle.Orange, color: Color.Black,
113  textAlignment: Alignment.Center, style: "OuterGlow")
114  {
115  ToolTip = TextManager.Get("hint.radiojammed")
116  };
117 
118  var buttonLeft = new GUIButton(new RectTransform(new Vector2(0.1f, 0.8f), channelSettingsContent.RectTransform), style: "DeviceButton")
119  {
120  PlaySoundOnSelect = false,
121  OnClicked = (btn, userdata) =>
122  {
124  {
125  SetChannel(radio.Channel - 1, setText: true);
126  SoundPlayer.PlayUISound(GUISoundType.PopupMenu);
127  }
128  return true;
129  }
130  };
131  var arrowIcon = new GUIImage(new RectTransform(new Vector2(0.4f), buttonLeft.RectTransform, Anchor.Center), style: "GUIButtonHorizontalArrow", scaleToFit: true)
132  {
133  Color = new Color(51, 59, 46),
134  SpriteEffects = Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally
135  };
136  arrowIcon.HoverColor = arrowIcon.PressedColor = arrowIcon.SelectedColor = arrowIcon.Color;
137 
138  channelText = new GUITextBox(new RectTransform(new Vector2(0.25f, 0.8f), channelSettingsContent.RectTransform), style: "DigitalFrameLight", textAlignment: Alignment.Center, font: GUIStyle.DigitalFont)
139  {
140  textFilterFunction = text =>
141  {
142  var str = new string(text.Where(c => char.IsNumber(c)).ToArray());
143  if (str.Length > 4) { str = str.Substring(0, 4); }
144  return str;
145  },
146  OnEnterPressed = (tb, text) =>
147  {
148  tb.Deselect();
149  return true;
150  }
151  };
152  Vector2 textSize = channelText.Font.MeasureString("0000");
153  channelText.TextBlock.ToolTip = TextManager.Get("currentradiochannel");
154  channelText.TextBlock.TextScale = Math.Min(channelText.Rect.Height / textSize.Y * 0.9f, 1.0f);
155  channelText.OnDeselected += (sender, key) =>
156  {
157  int.TryParse(channelText.Text, out int newChannel);
158  SetChannel(newChannel, setText: true);
159  SoundPlayer.PlayUISound(GUISoundType.PopupMenu);
160  };
161 
162  var buttonRight = new GUIButton(new RectTransform(new Vector2(0.1f, 0.8f), channelSettingsContent.RectTransform), style: "DeviceButton")
163  {
164  PlaySoundOnSelect = false,
165  OnClicked = (btn, userdata) =>
166  {
168  {
169  SetChannel(radio.Channel + 1, setText: true);
170  SoundPlayer.PlayUISound(GUISoundType.PopupMenu);
171  }
172  return true;
173  }
174  };
175  arrowIcon = new GUIImage(new RectTransform(new Vector2(0.4f), buttonRight.RectTransform, Anchor.Center), style: "GUIButtonHorizontalArrow", scaleToFit: true)
176  {
177  Color = new Color(51, 59, 46)
178  };
179  arrowIcon.HoverColor = arrowIcon.PressedColor = arrowIcon.PressedColor = arrowIcon.Color;
180 
181  var channelPicker = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.6f), channelSettingsContent.RectTransform), "InnerFrame");
182  channelPickerContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), channelPicker.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
183  {
184  Stretch = true
185  };
186  for (int i = 0; i < 10; i++)
187  {
188  new GUIButton(new RectTransform(new Vector2(0.1f, 1.0f), channelPickerContent.RectTransform), i.ToString(), style: "GUITextBlock")
189  {
190  TextColor = new Color(51, 59, 46),
191  SelectedTextColor = GUIStyle.Green,
192  UserData = i,
193  PlaySoundOnSelect = false,
194  OnClicked = (btn, userdata) =>
195  {
197  {
198  int index = (int)userdata;
199  if (channelMemPending)
200  {
201  int.TryParse(channelText.Text, out int newChannel);
202  radio.SetChannelMemory(index, newChannel);
203  btn.ToolTip = TextManager.GetWithVariables("radiochannelpreset",
204  ("[index]", index.ToString()),
205  ("[channel]", radio.GetChannelMemory(index).ToString()));
206  channelMemPending = false;
207  channelPickerContent.Children.First().CanBeFocused = true;
208  memButton.Enabled = true;
209  channelPickerContent.Flash(GUIStyle.Green);
210  channelText.Flash(GUIStyle.Green);
211  }
212  SetChannel(radio.GetChannelMemory(index), setText: true);
213  SoundPlayer.PlayUISound(GUISoundType.PopupMenu);
214  }
215  return true;
216  }
217  };
218  }
219 
220  memButton = new GUIButton(new RectTransform(new Vector2(0.2f, 0.9f), channelSettingsContent.RectTransform), style: "DeviceButton", text: TextManager.Get("saveradiochannelbutton"))
221  {
222  ToolTip = TextManager.Get("saveradiochannelbuttontooltip"),
223  OnClicked = (btn, userdata) =>
224  {
225  channelMemPending = true;
226  //don't allow storing a value in the first preset
227  channelPickerContent.Children.First().CanBeFocused = false;
228  foreach (GUIComponent channelButton in channelPickerContent.Children)
229  {
230  channelButton.Selected = false;
231  }
232  btn.Enabled = false;
233  return true;
234  }
235  };
236 
237  // ---------------------------------------------------------------------------------------------
238 
239  var bottomContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.125f), hideableElements.RectTransform, Anchor.BottomLeft), isHorizontal: true)
240  {
241  Stretch = true
242  };
243 
244  var dropdownRt = new RectTransform(new Vector2(0.1f, 1.0f), bottomContainer.RectTransform)
245  {
246  // The chat mode selection dropdown will take a maximum of 45% of the horizontal space
247  MaxSize = new Point((int)(0.45f * bottomContainer.RectTransform.NonScaledSize.X), int.MaxValue)
248  };
249  var chatModes = new ChatMode[] { ChatMode.Local, ChatMode.Radio };
250  ChatModeDropDown = new GUIDropDown(dropdownRt, elementCount: chatModes.Length, dropAbove: true)
251  {
252  OnSelected = (component, userdata) =>
253  {
254  GameMain.ActiveChatMode = (ChatMode)userdata;
255  if (InputBox != null && InputBox.Text.StartsWith(RadioChatString) && GameMain.ActiveChatMode == ChatMode.Local)
256  {
257  string text = InputBox.Text;
258  InputBox.Text = text.Remove(0, RadioChatString.Length);
259  }
260  return true;
261  }
262  };
263  float longestDropDownOption = 0.0f;
264  foreach (ChatMode mode in chatModes)
265  {
266  var text = TextManager.Get($"chatmode.{mode}");
267  ChatModeDropDown.AddItem(text, userData: mode);
268  if (ChatModeDropDown.ListBox.Content.GetChildByUserData(mode) is GUITextBlock textBlock)
269  {
270  if (textBlock.TextSize.X > longestDropDownOption)
271  {
272  longestDropDownOption = textBlock.TextSize.X;
273  }
274  }
275  }
276  ChatModeDropDown.SelectItem(GameMain.ActiveChatMode);
277 
278  float minDropDownWidth = longestDropDownOption + ChatModeDropDown.Padding.X +
279  (ChatModeDropDown.DropDownIcon?.RectTransform.NonScaledSize.X ?? 0) +
280  (ChatModeDropDown.DropDownIcon?.RectTransform.AbsoluteOffset.X ?? 0) * 2;
281  ChatModeDropDown.RectTransform.MinSize = new Point(
282  Math.Max((int)minDropDownWidth, ChatModeDropDown.RectTransform.MinSize.X),
283  ChatModeDropDown.RectTransform.MinSize.Y);
284 
285  InputBox = new GUITextBox(new RectTransform(new Vector2(0.9f, 1.0f), bottomContainer.RectTransform),
286  style: "ChatTextBox")
287  {
288  OverflowClip = true,
289  Font = GUIStyle.SmallFont,
290  MaxTextLength = ChatMessage.MaxLength
291  };
292 
294 
295  InputBox.OnDeselected += (gui, Keys) =>
296  {
297  ChatManager.Clear();
298  if (GUIFrame.IsParentOf(GUI.MouseOn))
299  {
300  CloseAfterMessageSent = false;
301  return;
302  }
304  if (string.IsNullOrEmpty(message))
305  {
307  {
308  _toggleOpen = false;
309  CloseAfterMessageSent = false;
310  }
311  }
312  };
313 
314  var chatSendButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.7f), InputBox.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight), style: "GUIButtonToggleRight");
315  chatSendButton.OnClicked += (GUIButton btn, object userdata) =>
316  {
318  return true;
319  };
320  chatSendButton.RectTransform.AbsoluteOffset = new Point((int)(InputBox.Rect.Height * 0.15f), 0);
322  = new Point((int)(InputBox.Rect.Width - chatSendButton.Rect.Width * 1.25f - InputBox.TextBlock.Padding.X - chatSendButton.RectTransform.AbsoluteOffset.X), int.MaxValue);
323 
324  showNewMessagesButton = new GUIButton(new RectTransform(new Vector2(1f, 0.075f), GUIFrame.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0.0f, 0.125f) }, TextManager.Get("chat.shownewmessages"));
325  showNewMessagesButton.OnClicked += (GUIButton btn, object userdata) =>
326  {
327  chatBox.ScrollBar.BarScrollValue = 1f;
328  showNewMessagesButton.Visible = false;
329  return true;
330  };
331 
332  showNewMessagesButton.Visible = false;
333  ToggleOpen = PreferChatBoxOpen = GameSettings.CurrentConfig.ChatOpen;
334  }
335 
336  public void Toggle()
337  {
339  CloseAfterMessageSent = false;
340  }
341 
342  public bool TypingChatMessage(GUITextBox textBox, string text)
343  {
344  string command = ChatMessage.GetChatMessageCommand(text, out _);
345  if (IsSinglePlayer)
346  {
347  //radio is the only allowed special message type in single player
348  if (command != "r" && command != "radio")
349  {
350  command = "";
351  }
352  }
353 
354  Color textColor;
355  switch (command)
356  {
357  case "r":
358  case "radio":
359  textColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio];
360  break;
361  case "d":
362  case "dead":
363  textColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
364  break;
365  default:
367  {
368  textColor = ChatMessage.MessageColor[(int)ChatMessageType.Dead];
369  }
370  else if (command != "") //PMing
371  {
372  textColor = ChatMessage.MessageColor[(int)ChatMessageType.Private];
373  }
374  else if (GameMain.ActiveChatMode == ChatMode.Radio)
375  {
376  textColor = ChatMessage.MessageColor[(int)ChatMessageType.Radio];
377  }
378  else
379  {
380  textColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
381  }
382  break;
383  }
384  textBox.TextColor = textBox.TextBlock.SelectedTextColor = textColor;
385 
386  return true;
387  }
388 
389  public void AddMessage(ChatMessage message)
390  {
392  {
393  var should = GameMain.LuaCs.Hook.Call<bool?>("chatMessage", message.Text, message.SenderClient, message.Type, message);
394  if (should != null && should.Value) { return; }
395  }
396 
397  while (chatBox.Content.CountChildren > 60)
398  {
399  chatBox.RemoveChild(chatBox.Content.Children.First());
400  }
401 
402  float prevSize = chatBox.BarSize;
403 
404  string displayedText = message.TranslatedText;
405 
406  string senderName = "";
407  Color senderColor = Color.White;
408  if (!string.IsNullOrWhiteSpace(message.SenderName))
409  {
410  senderName = (message.Type == ChatMessageType.Private ? "[PM] " : "") + message.SenderName;
411  }
412  if (message.SenderCharacter?.Info?.Job != null)
413  {
414  senderColor = Color.Lerp(message.SenderCharacter.Info.Job.Prefab.UIColor, Color.White, 0.25f);
415  }
416 
417  var msgHolder = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.0f), chatBox.Content.RectTransform, Anchor.TopCenter), style: null,
418  color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f);
419 
420  GUITextBlock senderNameTimestamp = new GUITextBlock(new RectTransform(new Vector2(0.98f, 0.0f), msgHolder.RectTransform) { AbsoluteOffset = new Point((int)(5 * GUI.Scale), 0) },
421  ChatMessage.GetTimeStamp(), textColor: Color.LightGray, font: GUIStyle.SmallFont, textAlignment: Alignment.TopLeft, style: null)
422  {
423  CanBeFocused = true
424  };
425  if (!string.IsNullOrEmpty(senderName))
426  {
427  var senderNameBlock = new GUIButton(new RectTransform(new Vector2(0.8f, 1.0f), senderNameTimestamp.RectTransform) { AbsoluteOffset = new Point((int)(senderNameTimestamp.TextSize.X), 0) },
428  senderName, textAlignment: Alignment.TopLeft, style: null, color: Color.Transparent)
429  {
430  TextBlock =
431  {
432  Padding = Vector4.Zero
433  },
434  Font = GUIStyle.SmallFont,
435  CanBeFocused = true,
437  UserData = message.SenderClient,
438  PlaySoundOnSelect = false,
439  OnClicked = (_, o) =>
440  {
441  if (!(o is Client client)) { return false; }
442  if (GameMain.NetLobbyScreen != null)
443  {
445  SoundPlayer.PlayUISound(GUISoundType.Select);
446  }
447  return true;
448  },
449  OnSecondaryClicked = (_, o) =>
450  {
451  if (!(o is Client client)) { return false; }
453  return true;
454  },
455  Text = senderName
456  };
457 
458  senderNameBlock.RectTransform.NonScaledSize = senderNameBlock.TextBlock.TextSize.ToPoint();
459  senderNameBlock.TextBlock.OverrideTextColor(senderColor);
460  if (senderNameBlock.UserData != null)
461  {
462  senderNameBlock.TextBlock.HoverTextColor = Color.White;
463  }
464  }
465 
466  var msgText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgHolder.RectTransform)
467  { AbsoluteOffset = new Point((int)(10 * GUI.Scale), senderNameTimestamp == null ? 0 : senderNameTimestamp.Rect.Height) },
468  RichString.Rich(displayedText), textColor: message.Color, font: GUIStyle.SmallFont, textAlignment: Alignment.TopLeft, style: null, wrap: true,
469  color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f)
470  {
471  UserData = message.SenderName,
472  CanBeFocused = false
473  };
474  msgText.CalculateHeightFromText();
475  if (msgText.RichTextData != null)
476  {
477  foreach (var data in msgText.RichTextData)
478  {
479  var clickableArea = new GUITextBlock.ClickableArea()
480  {
481  Data = data
482  };
483  if (GameMain.NetLobbyScreen != null && GameMain.NetworkMember != null)
484  {
485  clickableArea.OnClick = GameMain.NetLobbyScreen.SelectPlayer;
486  clickableArea.OnSecondaryClick = GameMain.NetLobbyScreen.ShowPlayerContextMenu;
487  }
488  msgText.ClickableAreas.Add(clickableArea);
489  }
490  }
491 
492  if (message is OrderChatMessage orderChatMsg &&
493  Character.Controlled != null &&
494  orderChatMsg.TargetCharacter == Character.Controlled)
495  {
496  msgHolder.Flash(Color.OrangeRed * 0.6f, flashDuration: 5.0f);
497  }
498  else
499  {
500  msgHolder.Flash(Color.Yellow * 0.6f);
501  }
502  msgHolder.RectTransform.SizeChanged += Recalculate;
503  Recalculate();
504  void Recalculate()
505  {
506  msgHolder.RectTransform.SizeChanged -= Recalculate;
507  //resize the holder to match the size of the message and add some spacing
508  msgText.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - msgText.RectTransform.AbsoluteOffset.X, int.MaxValue);
509  senderNameTimestamp.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - senderNameTimestamp.RectTransform.AbsoluteOffset.X, int.MaxValue);
510  msgHolder.Children.ForEach(c => (c as GUITextBlock)?.CalculateHeightFromText());
511  msgHolder.RectTransform.Resize(new Point(msgHolder.Rect.Width, msgHolder.Children.Sum(c => c.Rect.Height) + (int)(10 * GUI.Scale)), resizeChildren: false);
512  msgHolder.RectTransform.SizeChanged += Recalculate;
513  chatBox.RecalculateChildren();
514  chatBox.UpdateScrollBarSize();
515  }
516 
517  CoroutineManager.StartCoroutine(UpdateMessageAnimation(msgHolder, 0.5f));
518 
519  chatBox.UpdateScrollBarSize();
520 
521  if (chatBox.ScrollBar.Visible && chatBox.ScrollBar.BarScroll < 1f)
522  {
523  showNewMessagesButton.Visible = true;
524  }
525 
526  if (message.Type == ChatMessageType.Server && message.ChangeType != PlayerConnectionChangeType.None)
527  {
529  }
530 
531  if (!ToggleOpen)
532  {
533  var popupMsg = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: "GUIToolTip")
534  {
535  UserData = 0.0f,
536  CanBeFocused = false
537  };
538  var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), popupMsg.RectTransform, Anchor.Center));
539  Vector2 senderTextSize = Vector2.Zero;
540  if (!string.IsNullOrEmpty(senderName))
541  {
542  var senderText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
543  senderName, textColor: senderColor, style: null, font: GUIStyle.SmallFont)
544  {
545  CanBeFocused = false
546  };
547  senderTextSize = senderText.Font.MeasureString(senderText.WrappedText);
548  senderText.RectTransform.MinSize = new Point(0, senderText.Rect.Height);
549  }
550  var msgPopupText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
551  RichString.Rich(displayedText), textColor: message.Color, font: GUIStyle.SmallFont, textAlignment: Alignment.BottomLeft, style: null, wrap: true)
552  {
553  CanBeFocused = false
554  };
555  msgPopupText.RectTransform.MinSize = new Point(0, msgPopupText.Rect.Height);
556  Vector2 msgSize = msgPopupText.Font.MeasureString(msgPopupText.WrappedText);
557  int textWidth = (int)Math.Max(msgSize.X + msgPopupText.Padding.X + msgPopupText.Padding.Z, senderTextSize.X) + 10;
558  popupMsg.RectTransform.Resize(new Point((int)(textWidth / content.RectTransform.RelativeSize.X) , (int)((senderTextSize.Y + msgSize.Y) / content.RectTransform.RelativeSize.Y)), resizeChildren: true);
559  popupMsg.RectTransform.IsFixedSize = true;
560  content.Recalculate();
561  popupMessages.Add(popupMsg);
562  }
563 
564  if ((prevSize == 1.0f && chatBox.BarScroll == 0.0f) || (prevSize < 1.0f && chatBox.BarScroll == 1.0f)) { chatBox.BarScroll = 1.0f; }
565 
566  GUISoundType soundType = GUISoundType.ChatMessage;
567  if (message.Type == ChatMessageType.Radio)
568  {
569  soundType = GUISoundType.RadioMessage;
570  }
571  else if (message.Type == ChatMessageType.Dead)
572  {
573  soundType = GUISoundType.DeadMessage;
574  }
575 
576  SoundPlayer.PlayUISound(soundType);
577  }
578 
579  public void SetVisibility(bool visible)
580  {
581  GUIFrame.Parent.Visible = visible;
582  }
583 
584  private IEnumerable<CoroutineStatus> UpdateMessageAnimation(GUIComponent message, float animDuration)
585  {
586  float timer = 0.0f;
587  while (timer < animDuration)
588  {
589  timer += CoroutineManager.DeltaTime;
590  float wavePhase = timer / animDuration * MathHelper.TwoPi;
592  new Point((int)(Math.Sin(wavePhase) * (1.0f - timer / animDuration) * 50.0f), 0);
593  yield return CoroutineStatus.Running;
594  }
595  message.RectTransform.ScreenSpaceOffset = Point.Zero;
596  yield return CoroutineStatus.Success;
597  }
598 
599  private void SetUILayout()
600  {
602  GUIFrame.RectTransform.RelativeOffset = new Vector2(
603  HUDLayoutSettings.ChatBoxArea.X / (float)GameMain.GraphicsWidth,
604  HUDLayoutSettings.ChatBoxArea.Y / (float)GameMain.GraphicsHeight);
605  GUIFrame.RectTransform.NonScaledSize = HUDLayoutSettings.ChatBoxArea.Size;
606 
607  int toggleButtonWidth = (int)(ToggleButtonWidthRaw * GUI.Scale);
608  GUIFrame.RectTransform.NonScaledSize -= new Point(toggleButtonWidth, 0);
609  GUIFrame.RectTransform.AbsoluteOffset += new Point(toggleButtonWidth, 0);
610 
611  popupMessageOffset = GameMain.GameSession.CrewManager.ReportButtonFrame.Rect.Width + GUIFrame.Rect.Width + (int)(35 * GUI.Scale);
612  }
613 
614  public void Update(float deltaTime)
615  {
616  if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale)
617  {
618  SetUILayout();
619  screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
620  prevUIScale = GUI.Scale;
621  }
622 
623  if (showNewMessagesButton.Visible && chatBox.ScrollBar.BarScroll == 1f)
624  {
625  showNewMessagesButton.Visible = false;
626  }
627 
628  if (Screen.Selected == GameMain.GameScreen && GUI.KeyboardDispatcher.Subscriber == null)
629  {
630  if (PlayerInput.KeyHit(InputType.ToggleChatMode))
631  {
632  try
633  {
634  var mode = GameMain.ActiveChatMode switch
635  {
636  ChatMode.Local => ChatMode.Radio,
637  ChatMode.Radio => ChatMode.Local,
638  _ => throw new NotImplementedException()
639  };
640  ChatModeDropDown.SelectItem(mode);
641  // TODO: Play a sound?
642  }
643  catch (NotImplementedException)
644  {
645  DebugConsole.ThrowError($"Error toggling chat mode: not implemented for current mode \"{GameMain.ActiveChatMode}\"");
646  }
647  }
648  else if (PlayerInput.KeyHit(InputType.ChatBox))
649  {
650  Toggle();
651  }
652  }
653 
654  if (ToggleButton != null)
655  {
657  ToggleButton.RectTransform.AbsoluteOffset = new Point(GUIFrame.Rect.Right, GUIFrame.Rect.Y + HUDLayoutSettings.ChatBoxArea.Height - ToggleButton.Rect.Height);
658  }
659 
660  if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio, ignoreJamming: true))
661  {
662  if (prevRadio != radio)
663  {
664  foreach (GUIComponent presetButton in channelPickerContent.Children)
665  {
666  int index = (int)presetButton.UserData;
667  presetButton.ToolTip = TextManager.GetWithVariables("radiochannelpreset",
668  ("[index]", index.ToString()),
669  ("[channel]", radio.GetChannelMemory(index).ToString()));
670  }
671  SetChannel(radio.Channel, setText: true);
672  prevRadio = radio;
673  }
674  if (channelMemPending)
675  {
676  if (channelPickerContent.FlashTimer <= 0)
677  {
678  channelPickerContent.Flash(GUIStyle.Green, flashRectInflate: new Vector2(GUI.Scale * 5.0f));
679  }
680  if (PlayerInput.PrimaryMouseButtonClicked() && !GUI.IsMouseOn(channelPickerContent))
681  {
682  channelPickerContent.Children.First().CanBeFocused = true;
683  channelMemPending = false;
684  memButton.Enabled = true;
685  SetChannel(radio.Channel, setText: true);
686  }
687  }
688  channelSettingsFrame.Visible = true;
689  radioJammedWarning.Visible = radio is { JamTimer: > 0 };
690  }
691  else
692  {
693  radioJammedWarning.Visible = channelSettingsFrame.Visible = false;
694  channelPickerContent.Children.First().CanBeFocused = true;
695  channelMemPending = false;
696  memButton.Enabled = true;
697  }
698 
699  if (ToggleOpen)
700  {
701  GUIFrame.CanBeFocused = true;
702  openState += deltaTime * 5.0f;
703  //delete all popup messages when the chatbox is open
704  foreach (var popupMsg in popupMessages)
705  {
706  popupMsg.Parent.RemoveChild(popupMsg);
707  }
708  popupMessages.Clear();
709  }
710  else
711  {
712  GUIFrame.CanBeFocused = false;
713  openState -= deltaTime * 5.0f;
714 
715  int yOffset = 0;
716  foreach (var popupMsg in popupMessages)
717  {
718  float msgTimer = (float)popupMsg.UserData;
719 
720  int targetYOffset = (int)MathHelper.Lerp(popupMsg.RectTransform.ScreenSpaceOffset.Y, yOffset, deltaTime * 10.0f);
721 
722  if (popupMsg == popupMessages.First())
723  {
724  popupMsg.UserData = msgTimer + deltaTime * Math.Max(popupMessages.Count / 2, 1);
725  if (msgTimer > PopupMessageDuration)
726  {
727  //move the message out of the screen and delete it
728  popupMsg.RectTransform.ScreenSpaceOffset =
729  new Point((int)MathHelper.SmoothStep(popupMessageOffset, 10, (msgTimer - PopupMessageDuration) * 5.0f), targetYOffset);
730  if (msgTimer > PopupMessageDuration + 0.2f)
731  {
732  popupMsg.Parent?.RemoveChild(popupMsg);
733  }
734  }
735  }
736  if (msgTimer < PopupMessageDuration)
737  {
738  if (popupMsg != popupMessages.First()) { popupMsg.UserData = Math.Min(msgTimer + deltaTime, 1.0f); }
739  //move the message on the screen
740  popupMsg.RectTransform.ScreenSpaceOffset = new Point(
741  (int)MathHelper.SmoothStep(0, popupMessageOffset, msgTimer * 5.0f), targetYOffset);
742  }
743  yOffset += popupMsg.Rect.Height + GUI.IntScale(10);
744  }
745 
746  popupMessages.RemoveAll(p => p.Parent == null);
747 
748  //make the first popup message visible
749  /*var popupMsg = popupMessages.Count > 0 ? popupMessages.Peek() : null;
750  if (popupMsg != null)
751  {
752  popupMsg.Visible = true;
753  //popup messages appear and disappear faster when there's more pending messages
754  popupMessageTimer += deltaTime * popupMessages.Count * popupMessages.Count;
755  if (popupMessageTimer > PopupMessageDuration)
756  {
757  //move the message out of the screen and delete it
758  popupMsg.RectTransform.ScreenSpaceOffset =
759  new Point((int)MathHelper.SmoothStep(popupMessageOffset, 10, (popupMessageTimer - PopupMessageDuration) * 5.0f), 0);
760  if (popupMessageTimer > PopupMessageDuration + 1.0f)
761  {
762  popupMessageTimer = 0.0f;
763  popupMsg.Parent.RemoveChild(popupMsg);
764  popupMessages.Dequeue();
765  }
766  }
767  else
768  {
769  //move the message on the screen
770  popupMsg.RectTransform.ScreenSpaceOffset = new Point(
771  (int)MathHelper.SmoothStep(0, popupMessageOffset, popupMessageTimer * 5.0f), 0);
772  }
773  }*/
774  }
775  openState = MathHelper.Clamp(openState, 0.0f, 1.0f);
776  int hiddenBoxOffset = -(GUIFrame.Rect.Width);
778  new Point((int)MathHelper.SmoothStep(hiddenBoxOffset, 0, openState), 0);
779  hideableElements.Visible = openState > 0.0f;
780  }
781 
782  private void SetChannel(int channel, bool setText)
783  {
785  {
786  radio.Channel = channel;
787  GameMain.Client?.CreateEntityEvent(radio.Item, new Item.ChangePropertyEventData(radio.SerializableProperties["channel".ToIdentifier()], radio));
788 
789  if (setText)
790  {
791  string text = radio.Channel.ToString().PadLeft(4, '0');
792  if (channelText.Text != text) { channelText.Text = text; }
793 
794  }
795  if (!channelMemPending)
796  {
797  foreach (GUIComponent channelButton in channelPickerContent.Children)
798  {
799  int buttonIndex = (int)channelButton.UserData;
800  channelButton.Selected = radio.GetChannelMemory(buttonIndex) == channel;
801  }
802  }
803  }
804  }
805 
807 
808  public struct ChatKeyStates
809  {
810  public bool ActiveChatKeyHit { get; set; }
811  public bool LocalChatKeyHit { get; set; }
812  public bool RadioChatKeyHit { get; set; }
814 
815  private ChatKeyStates(bool active, bool local, bool radio)
816  {
819  RadioChatKeyHit = radio;
820  }
821 
823  {
824  return new ChatKeyStates(PlayerInput.KeyHit(InputType.ActiveChat),
827  }
828 
829  public (bool active, bool local, bool radio) Deconstruct()
830  {
832  }
833  }
834 
835  public void ApplySelectionInputs(GUITextBox inputBox, bool selectInputBox, ChatKeyStates chatKeyStates)
836  {
837  inputBox ??= InputBox;
838  var (activeChatKeyHit, localChatKeyHit, radioChatKeyHit) = chatKeyStates.Deconstruct();
839  if (localChatKeyHit || (activeChatKeyHit && GameMain.ActiveChatMode == ChatMode.Local))
840  {
841  ChatModeDropDown.SelectItem(ChatMode.Local);
842  inputBox.AddToGUIUpdateList();
843  GUIFrame.Flash(Color.DarkGreen, 0.5f);
844  if (!ToggleOpen)
845  {
847  ToggleOpen = true;
848  }
849  if (selectInputBox)
850  {
851  inputBox.Select(inputBox.Text.Length);
852  }
853  }
854  else if (radioChatKeyHit || (activeChatKeyHit && GameMain.ActiveChatMode == ChatMode.Radio))
855  {
856  ChatModeDropDown.SelectItem(ChatMode.Radio);
857  inputBox.AddToGUIUpdateList();
858  GUIFrame.Flash(Color.YellowGreen, 0.5f);
859  if (!ToggleOpen)
860  {
862  ToggleOpen = true;
863  }
864  if (selectInputBox)
865  {
866  inputBox.Select(inputBox.Text.Length);
867  }
868  }
869  }
870  }
871 }
GUIFrame GUIFrame
Definition: ChatBox.cs:59
void ApplySelectionInputs()
GUIButton ToggleButton
Definition: ChatBox.cs:66
void SetVisibility(bool visible)
Definition: ChatBox.cs:579
GUITextBox.OnEnterHandler OnEnterMessage
Definition: ChatBox.cs:54
static bool PreferChatBoxOpen
Definition: ChatBox.cs:34
bool IsSinglePlayer
Definition: ChatBox.cs:20
void Update(float deltaTime)
Definition: ChatBox.cs:614
const int ToggleButtonWidthRaw
Definition: ChatBox.cs:82
GUITextBox InputBox
Definition: ChatBox.cs:61
bool CloseAfterMessageSent
Definition: ChatBox.cs:36
const string RadioChatString
Definition: ChatBox.cs:13
readonly ChatManager ChatManager
Definition: ChatBox.cs:18
void ApplySelectionInputs(GUITextBox inputBox, bool selectInputBox, ChatKeyStates chatKeyStates)
Definition: ChatBox.cs:835
ChatBox(GUIComponent parent, bool isSinglePlayer)
Definition: ChatBox.cs:87
void AddMessage(ChatMessage message)
Definition: ChatBox.cs:389
bool TypingChatMessage(GUITextBox textBox, string text)
Definition: ChatBox.cs:342
A class used for handling special key actions in chat boxes. For example tab completion or up/down ar...
Definition: ChatManager.cs:14
static void RegisterKeys(GUITextBox element, ChatManager manager)
Registers special input actions to the selected input field
Definition: ChatManager.cs:53
void Clear()
Call this function whenever we should stop doing special stuff and return normal behavior....
Definition: ChatManager.cs:112
static CoroutineStatus Running
override RichString ToolTip
Definition: GUIButton.cs:150
virtual void Flash(Color? color=null, float flashDuration=1.5f, bool useRectangleFlash=false, bool useCircularFlash=false, Vector2? flashRectInflate=null)
virtual void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
bool IsParentOf(GUIComponent component, bool recursive=true)
Definition: GUIComponent.cs:75
GUIComponent GetChildByUserData(object obj)
Definition: GUIComponent.cs:66
virtual RichString ToolTip
virtual Rectangle Rect
virtual Color HoverColor
RectTransform RectTransform
SpriteEffects SpriteEffects
IEnumerable< GUIComponent > Children
Definition: GUIComponent.cs:29
GUIComponent AddItem(LocalizedString text, object userData=null, LocalizedString toolTip=null, Color? color=null, Color? textColor=null)
Definition: GUIDropDown.cs:270
Vector2 MeasureString(LocalizedString str, bool removeExtraSpacing=false)
Definition: GUIPrefab.cs:284
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
Definition: GUIListBox.cs:42
override GUIFont Font
Definition: GUITextBlock.cs:66
TextBoxEvent OnDeselected
Definition: GUITextBox.cs:17
delegate bool OnEnterHandler(GUITextBox textBox, string text)
void Select(int forcedCaretIndex=-1, bool ignoreSelectSound=false)
Definition: GUITextBox.cs:377
OnEnterHandler OnEnterPressed
Definition: GUITextBox.cs:29
GUITextBlock TextBlock
Definition: GUITextBox.cs:130
static int GraphicsWidth
Definition: GameMain.cs:162
static NetLobbyScreen NetLobbyScreen
Definition: GameMain.cs:55
static int GraphicsHeight
Definition: GameMain.cs:168
static ChatMode ActiveChatMode
Definition: GameMain.cs:218
static bool IsSingleplayer
Definition: GameMain.cs:34
static GameScreen GameScreen
Definition: GameMain.cs:52
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static GameClient Client
Definition: GameMain.cs:188
static LuaCsSetup LuaCs
Definition: GameMain.cs:26
JobPrefab Prefab
Definition: Job.cs:18
object Call(string name, params object[] args)
static string GetChatMessageCommand(string message, out string messageWithoutCommand)
static bool CanUseRadio(Character sender, bool ignoreJamming=false)
override void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData=null)
Definition: GameClient.cs:2732
Point ScreenSpaceOffset
Screen space offset. From top left corner. In pixels.
Point AbsoluteOffset
Absolute in pixels but relative to the anchor point. Calculated away from the anchor point,...
RectTransform?? Parent
Point?? MinSize
Min size in pixels. Does not affect scaling.
Vector2 RelativeOffset
Defined as portions of the parent size. Also the direction of the offset is relative,...
Point NonScaledSize
Size before scale multiplications.
Point?? MaxSize
Max size in pixels. Does not affect scaling.
static RichString Rich(LocalizedString str, Func< string, string >? postProcess=null)
Definition: RichString.cs:67
static void StorePlayerConnectionChangeMessage(ChatMessage message)
Definition: TabMenu.cs:1540
GUISoundType
Definition: GUI.cs:21
static ChatKeyStates GetChatKeyStates()
Definition: ChatBox.cs:822
bool bool bool radio Deconstruct()
Definition: ChatBox.cs:829
void ShowPlayerContextMenu(GUITextBlock component, GUITextBlock.ClickableArea area)
void SelectPlayer(GUITextBlock component, GUITextBlock.ClickableArea area)