5 using Microsoft.Xna.Framework;
6 using Microsoft.Xna.Framework.Graphics;
7 using Microsoft.Xna.Framework.Input;
9 using System.Collections.Generic;
11 using System.Xml.Linq;
15 partial class CrewManager
17 private Point screenResolution;
21 private bool dropOrder;
22 private int framesToSkip = 2;
23 private float dragOrderTreshold;
24 private Vector2 dragPoint = Vector2.Zero;
33 private float crewListOpenState;
34 private bool _isCrewMenuOpen =
true;
35 private Point crewListEntrySize;
37 private readonly List<GUITickBox> traitorButtons =
new List<GUITickBox>();
44 private float prevUIScale;
54 get {
return _isCrewMenuOpen; }
57 if (_isCrewMenuOpen == value) {
return; }
58 _isCrewMenuOpen = value;
71 const float CommandNodeAnimDuration = 0.2f;
75 private Sprite jobIndicatorBackground, previousOrderArrow, cancelIcon;
82 : this(isSinglePlayer)
87 partial
void InitProjectSpecific()
96 crewArea =
new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.
RectTransform), style:
null, color: Color.Transparent)
103 crewList =
new GUIListBox(
new RectTransform(Vector2.One, crewArea.
RectTransform), style:
null, isScrollBarOnDefaultSide:
false)
105 AutoHideScrollBar =
false,
106 CanBeFocused =
false,
107 CurrentDragMode = GUIListBox.
DragMode.DragWithinBox,
108 CanInteractWhenUnfocusable =
true,
109 OnSelected = (component, userData) =>
false,
110 SelectMultiple =
false,
111 Spacing = (
int)(GUI.Scale * 10),
112 OnRearranged = OnCrewListRearranged
115 jobIndicatorBackground =
new Sprite(
"Content/UI/CommandUIAtlas.png",
new Rectangle(0, 512, 128, 128));
116 previousOrderArrow =
new Sprite(
"Content/UI/CommandUIAtlas.png",
new Rectangle(128, 512, 128, 128));
117 cancelIcon =
new Sprite(
"Content/UI/CommandUIAtlas.png",
new Rectangle(512, 384, 128, 128));
120 crewListEntrySize =
new Point(crewList.
Content.
Rect.Width - HUDLayoutSettings.Padding, 0);
121 int crewListEntryMinHeight = 32;
122 crewListEntrySize.Y = Math.Max(crewListEntryMinHeight, (
int)(crewListEntrySize.X / 8f));
123 float charactersPerView = crewList.
Content.
Rect.Height / (float)(crewListEntrySize.Y + crewList.
Spacing);
124 int adjustedHeight = (int)Math.Ceiling(crewList.
Content.
Rect.Height / Math.Round(charactersPerView)) - crewList.
Spacing;
125 if (adjustedHeight < crewListEntryMinHeight) { adjustedHeight = (int)Math.Ceiling(crewList.
Content.
Rect.Height / Math.Floor(charactersPerView)) - crewList.
Spacing; }
126 crewListEntrySize.Y = adjustedHeight;
136 OnEnterMessage = (textbox, text) =>
147 if (!
string.IsNullOrWhiteSpace(text))
152 bool isUsingRadioMode = GameMain.ActiveChatMode ==
ChatMode.Radio;
153 bool containsRadioCommand = msgCommand ==
"r" || msgCommand ==
"radio";
164 headset.TransmitSignal(s, sentFromChat:
true);
187 chatBox.ToggleButton =
new GUIButton(
new RectTransform(
new Point((
int)(182f * GUI.Scale * 0.4f), (
int)(99f * GUI.Scale * 0.4f)), chatBox.GUIFrame.Parent.RectTransform), style:
"ChatToggleButton")
189 ToolTip = TextManager.GetWithVariable(
"hudbutton.chatbox",
"[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(
InputType.ChatBox)),
190 ClampMouseRectToParent =
false
192 chatBox.ToggleButton.RectTransform.AbsoluteOffset =
new Point(0, HUDLayoutSettings.ChatBoxArea.Height - chatBox.ToggleButton.Rect.Height);
193 chatBox.ToggleButton.OnClicked += (GUIButton btn,
object userdata) =>
200 var reports = OrderPrefab.Prefabs.Where(o => o.IsVisibleAsReportButton).OrderBy(o => o.Identifier).ToArray();
203 DebugConsole.ThrowError(
"No valid orders for report buttons found! Cannot create report buttons. The orders for the report buttons must have 'targetallcharacters' attribute enabled and a valid 'symbolsprite' defined.");
208 new Point((HUDLayoutSettings.ChatBoxArea.Height - chatBox.ToggleButton.Rect.Height - (
int)((reports.Length - 1) * 5 * GUI.Scale)) / reports.Length, HUDLayoutSettings.ChatBoxArea.Height - chatBox.ToggleButton.Rect.Height), guiFrame.
RectTransform))
210 AbsoluteSpacing = (int)(5 * GUI.Scale),
211 UserData =
"reportbuttons",
212 CanBeFocused =
false,
222 screenResolution =
new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
223 prevUIScale = GUI.Scale;
236 OnClicked = (button, userData) =>
242 if (crewManager !=
null)
245 crewManager.SetCharacterOrder(
null, order);
250 UserData = orderPrefab,
251 ClampMouseRectToParent =
false
253 btn.ToolTip =
RichString.
Rich($
"‖color:{XMLExtensions.ColorToString(orderPrefab.Color)}‖{orderPrefab.Name}‖color:end‖\n{TextManager.Get("draganddropreports
")}");
255 if (crewManager !=
null)
257 btn.OnButtonDown = () =>
259 crewManager.dragOrderTreshold = Math.Max(btn.Rect.Width, btn.Rect.Height) / 2f;
260 crewManager.DraggedOrderPrefab = orderPrefab;
261 crewManager.dropOrder =
false;
262 crewManager.framesToSkip = 2;
263 crewManager.dragPoint = btn.Rect.Center.ToVector2();
270 Color = GUIStyle.Red * 0.8f,
271 HoverColor = GUIStyle.Red * 1.0f,
272 PressedColor = GUIStyle.Red * 0.6f,
273 UserData =
"highlighted",
274 CanBeFocused =
false,
280 Color = orderPrefab.
Color,
281 HoverColor = Color.Lerp(orderPrefab.
Color, Color.White, 0.5f),
282 ToolTip = btn.ToolTip,
283 SpriteEffects = SpriteEffects.FlipHorizontally,
284 UserData = orderPrefab
291 #region Character list management
295 return crewArea.
Rect;
303 if (character ==
null) {
return null; }
308 style:
"CrewListBackground")
310 UserData = character,
311 OnSecondaryClicked = (comp, data) =>
313 if (data ==
null) {
return false; }
322 SetCharacterComponentTooltip(background);
324 var iconRelativeWidth = (float)crewListEntrySize.Y / background.Rect.Width;
327 new RectTransform(Vector2.One, parent: background.RectTransform),
329 childAnchor:
Anchor.CenterLeft)
331 CanBeFocused =
false,
332 RelativeSpacing = 0.1f * iconRelativeWidth,
336 var commandButtonAbsoluteHeight = Math.Min(40.0f, 0.67f * background.Rect.Height);
337 var paddingRelativeWidth = 0.35f * commandButtonAbsoluteHeight / background.Rect.Width;
340 new GUIFrame(
new RectTransform(
new Vector2(paddingRelativeWidth, 1.0f), layoutGroup.RectTransform), style:
null)
346 bool isJobIconVisible = crewListEntrySize.X >= 220;
348 if (isJobIconVisible)
350 var jobIconBackground =
new GUIImage(
351 new RectTransform(
new Vector2(0.8f * iconRelativeWidth, 0.8f), layoutGroup.RectTransform),
352 jobIndicatorBackground,
355 CanBeFocused =
false,
361 new RectTransform(Vector2.One, jobIconBackground.RectTransform),
365 CanBeFocused =
false,
374 int iconsVisible = isJobIconVisible ? 6 : 5;
375 var nameRelativeWidth = 1.0f
377 - paddingRelativeWidth
379 - (iconsVisible * 0.8f * iconRelativeWidth)
381 - (0.1f * iconRelativeWidth)
383 - (7 * layoutGroup.RelativeSpacing);
384 nameRelativeWidth = Math.Max(nameRelativeWidth, 0.25f);
386 var font = layoutGroup.Rect.Width < 150 ? GUIStyle.SmallFont : GUIStyle.Font;
389 new Vector2(nameRelativeWidth, 1.0f),
390 layoutGroup.RectTransform)
392 MaxSize = new Point(150, background.Rect.Height)
397 CanBeFocused =
false,
400 nameBlock.
Text = ToolBox.LimitString(character.
Name, font, (
int)nameBlock.Rect.Width);
403 new RectTransform(
new Vector2(0.1f * iconRelativeWidth, 0.5f), layoutGroup.RectTransform),
404 style:
"VerticalLine")
409 var orderGroup =
new GUILayoutGroup(
new RectTransform(
new Vector2(3 * 0.8f * iconRelativeWidth, 0.8f), parent: layoutGroup.RectTransform), isHorizontal:
true, childAnchor:
Anchor.CenterLeft)
411 CanBeFocused =
false,
416 var currentOrderList =
new GUIListBox(
new RectTransform(
new Vector2(0.0f, 1.0f), parent: orderGroup.RectTransform), isHorizontal:
true, style:
null)
418 AllowMouseWheelScroll =
false,
420 KeepSpaceForScrollBar =
false,
421 OnRearranged = OnOrdersRearranged,
422 ScrollBarVisible =
false,
426 currentOrderList.RectTransform.IsFixedSize =
true;
427 currentOrderList.OnAddedToGUIUpdateList += (component) =>
432 list.CurrentDragMode =
CanIssueOrders && list.Content.CountChildren > 1
441 CanBeFocused =
false,
445 var extraIconFrame =
new GUIFrame(
new RectTransform(
new Vector2(0.8f * iconRelativeWidth * 2, 0.8f), layoutGroup.RectTransform), style:
null)
447 CanBeFocused =
false,
448 UserData =
"extraicons"
453 CanBeFocused =
false,
454 UserData =
"soundicons",
458 new RectTransform(Vector2.One, soundIconParent.RectTransform),
459 GUIStyle.GetComponentStyle(
"GUISoundIcon").GetDefaultSprite(),
462 CanBeFocused =
false,
467 new RectTransform(Vector2.One, soundIconParent.RectTransform),
468 "GUISoundIconDisabled",
472 UserData =
"soundicondisabled",
476 new GUIButton(
new RectTransform(
new Point((
int)commandButtonAbsoluteHeight), background.RectTransform), style:
"CrewListCommandButton")
478 ToolTip = TextManager.Get(
"inputtype.command"),
479 OnClicked = (component, userData) =>
482 CreateCommandUI(character);
490 CanBeFocused =
false,
491 UserData =
"objectiveicon",
498 if (targetClient !=
null)
502 var voteTraitorBtn =
new GUITickBox(
new RectTransform(Vector2.One, extraIconFrame.RectTransform,
Anchor.CenterRight, scaleBasis:
ScaleBasis.Smallest), label:
string.Empty, style:
"TraitorVoteButton")
504 UserData = character,
507 $
"‖color:{XMLExtensions.ToStringHex(GUIStyle.TextColorBright)}‖{TextManager.Get("traitor.blamebutton
")}‖color:end‖\n"
508 + TextManager.Get(
"traitor.blamebutton.tooltip")),
511 foreach (var traitorBtn
in traitorButtons)
514 if (traitorBtn != obj) { traitorBtn.SetSelected(
false, callOnSelected:
false); }
520 traitorButtons.Add(voteTraitorBtn);
527 crewManagement.RefreshUI();
538 traitorButtons.RemoveAll(t => t.IsChildOf(component, recursive:
true));
542 crewManagement.RefreshUI();
546 private static void SetCharacterComponentTooltip(
GUIComponent characterComponent)
548 if (!(characterComponent?.UserData is
Character character)) {
return; }
549 if (character.Info?.Job?.Prefab ==
null) {
return; }
551 LocalizedString tooltip = TextManager.GetWithVariables(
"crewlistelementtooltip",
552 (
"[name]", character.Name),
553 (
"[job]", character.Info.Job.Name));
554 string color = XMLExtensions.ColorToString(character.Info.Job.Prefab.UIColor);
555 RichString richToolTip = RichString.Rich($
"‖color:{color}‖"+tooltip+
"‖color:end‖");
556 characterComponent.
ToolTip = richToolTip;
564 if (!AllowCharacterSwitch) {
return false; }
565 if (!(selection is
Character character) || character.IsDead || character.IsUnconscious) {
return false; }
566 if (!character.IsOnPlayerTeam) {
return false; }
568 SelectCharacter(character);
569 if (GUI.KeyboardDispatcher.Subscriber == crewList) { GUI.KeyboardDispatcher.Subscriber =
null; }
579 if (characterInfos.Contains(revivedCharacter.
Info)) { AddCharacter(revivedCharacter); }
586 CoroutineManager.StartCoroutine(KillCharacterAnim(characterComponent));
588 RemoveCharacter(killedCharacter, resetCrewListIndex: resetCrewListIndex);
591 private IEnumerable<CoroutineStatus> KillCharacterAnim(
GUIComponent component)
593 List<GUIComponent> components = component.
GetAllChildren().ToList();
594 components.Add(component);
595 components.RemoveAll(c =>
597 c.UserData as
string ==
"soundicondisabled");
598 components.ForEach(c => c.Color = Color.DarkRed);
603 float hideDuration = 1.0f;
604 while (timer < hideDuration)
608 comp.
Color = Color.Lerp(Color.DarkRed, Color.Transparent, timer / hideDuration);
611 timer += CoroutineManager.DeltaTime;
612 yield
return CoroutineStatus.Running;
619 yield
return CoroutineStatus.Success;
622 partial
void RenameCharacterProjSpecific(CharacterInfo characterInfo)
625 SetCharacterComponentTooltip(characterComponent);
626 if (!(characterComponent.FindChild(
"name", recursive:
true) is GUITextBlock nameBlock)) {
return; }
627 nameBlock.Text = ToolBox.LimitString(characterInfo.Name, nameBlock.Font, nameBlock.Rect.Width);
630 private void OnCrewListRearranged(GUIListBox crewList,
object draggedElementData)
632 if (crewList != this.crewList) {
return; }
633 if (draggedElementData is not Character) {
return; }
634 if (!IsSinglePlayer) {
return; }
635 if (crewList.HasDraggedElementIndexChanged)
637 UpdateCrewListIndices();
641 CharacterClicked(crewList.DraggedElement, draggedElementData);
645 private void ResetCrewListIndex(Character c)
647 if (c?.Info ==
null) {
return; }
648 c.Info.CrewListIndex = -1;
649 UpdateCrewListIndices();
652 private void UpdateCrewListIndices()
654 if (crewList ==
null) {
return; }
655 for (
int i = 0; i < crewList.Content.CountChildren; i++)
657 var characterComponent = crewList.Content.GetChild(i);
658 if (characterComponent?.UserData is not Character c) {
continue; }
659 if (c.Info ==
null) {
continue; }
660 c.Info.CrewListIndex = i;
664 private void SortCrewList()
666 if (crewList ==
null) {
return; }
667 crewList.Content.RectTransform.SortChildren((x, y) =>
669 var infoX = (x.GUIComponent.UserData as
Character)?.Info?.CrewListIndex;
670 var infoY = (y.GUIComponent.UserData as
Character)?.Info?.CrewListIndex;
673 return infoY.HasValue ? infoX.Value.CompareTo(infoY.Value) : -1;
677 return infoY.HasValue ? 1 : 0;
680 UpdateCrewListIndices();
692 AddSinglePlayerChatMessage(senderName.
Value, text.
Value, messageType, sender);
699 DebugConsole.ThrowError(
"Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace.CleanupStackTrace());
702 if (
string.IsNullOrEmpty(text)) {
return; }
707 if (!character.IsBot)
709 character.TextChatVolume = 1f;
719 DebugConsole.ThrowError(
"Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace.CleanupStackTrace());
722 if (
string.IsNullOrEmpty(message.
Text)) {
return; }
731 partial
void CreateRandomConversation()
738 List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
739 c.AIController is HumanAIController &&
741 c.SpeechImpediment <= 100.0f &&
742 c.CharacterHealth.GetAllAfflictions(a => a is AfflictionHusk huskInfection && huskInfection.Prefab is AfflictionPrefabHusk { CauseSpeechImpediment: true }).None());
743 pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
752 if (client?.
Character ==
null) {
return; }
757 var soundIconDisabled = soundIcons.FindChild(
"soundicondisabled");
758 soundIcon.Visible = !muted && !mutedLocally;
759 soundIconDisabled.Visible = muted || mutedLocally;
760 soundIconDisabled.ToolTip = TextManager.Get(mutedLocally ?
"MutedLocally" :
"MutedGlobally");
774 if (character ==
null || character.
IsBot) {
return; }
778 soundIcon.Color = Color.White;
786 return characterComponent?
792 private GUIComponent GetSoundIconParent(Character character)
794 return GetSoundIconParent(crewList?.Content.GetChildByUserData(character));
799 #region Crew List Order Displayment
822 foreach (var stackedItem
in item.GetStackedItems())
826 HintManager.OnItemMarkedForDeconstruction(order.
OrderGiver);
830 foreach (var stackedItem
in item.GetStackedItems())
842 if (ignorable is
Item item)
844 foreach (var stackedItem
in item.GetStackedItems())
852 ignorable.OrderedToBeIgnored = order.
Identifier == Tags.IgnoreThis;
853 AddOrder(order.
Clone(), fadeOutTime:
null);
858 var wallSectionIndex = order.
WallSectionIndex ?? s.Sections.IndexOf(wallContext);
859 ws = s.GetSection(wallSectionIndex);
863 AddOrder(order.
WithWallSection(s, wallSectionIndex), fadeOutTime:
null);
876 hull = item.CurrentHull;
898 if (character ==
null) {
return; }
902 bool isGivingOrderToSelf = orderGiver == character;
903 character.
SetOrder(order, isNewOrder, speak: !isGivingOrderToSelf);
904 string message = order?.
GetChatMessage(character.
Name, orderGiver?.CurrentHull?.DisplayName?.Value, isGivingOrderToSelf, orderOption: order?.
Option ?? Identifier.Empty, isNewOrder: isNewOrder);
905 orderGiver?.Speak(message);
907 else if (orderGiver !=
null)
920 if (character ==
null) {
return; }
924 if (characterComponent ==
null) {
return; }
926 var currentOrderIconList = GetCurrentOrderIconList(characterComponent);
927 var currentOrderIcons = currentOrderIconList.Content.Children;
928 var iconsToRemove =
new List<GUIComponent>();
929 var newPreviousOrders =
new List<Order>();
930 bool updatedExistingIcon =
false;
932 foreach (var icon
in currentOrderIcons)
934 var orderInfo = (
Order)icon.UserData;
935 var matchingOrder = character.GetCurrentOrder(orderInfo);
936 if (matchingOrder is
null)
938 iconsToRemove.Add(icon);
939 newPreviousOrders.Add(orderInfo);
941 else if (orderInfo.MatchesOrder(order))
943 icon.UserData = order.Clone();
946 image.Sprite = GetOrderIconSprite(order);
947 image.ToolTip = CreateOrderTooltip(order);
949 updatedExistingIcon =
true;
952 iconsToRemove.ForEach(c => currentOrderIconList.RemoveChild(c));
956 var previousOrderIconGroup = GetPreviousOrderIconGroup(characterComponent);
957 var previousOrderIcons = previousOrderIconGroup.Children;
958 foreach (var icon
in previousOrderIcons)
960 var orderInfo = (
Order)icon.UserData;
961 if (orderInfo.MatchesOrder(order))
963 previousOrderIconGroup.RemoveChild(icon);
969 if (updatedExistingIcon)
974 for (
int i = newPreviousOrders.Count - 1; i >= 0; i--)
976 AddPreviousOrderIcon(character, characterComponent, newPreviousOrders[i]);
979 if (order ==
null || order.Identifier == dismissedOrderPrefab.Identifier || updatedExistingIcon)
985 int orderIconCount = currentOrderIconList.Content.CountChildren + previousOrderIconGroup.CountChildren;
988 RemoveLastOrderIcon(characterComponent);
992 Point size =
new Point((
int)nodeWidth, currentOrderIconList.RectTransform.NonScaledSize.Y);
993 var nodeIcon = CreateNodeIcon(size, currentOrderIconList.Content.RectTransform, GetOrderIconSprite(order), order.Color, tooltip: CreateOrderTooltip(order));
994 nodeIcon.UserData = order.Clone();
995 nodeIcon.OnSecondaryClicked = (image, userData) =>
997 if (!CanIssueOrders) {
return false; }
998 var orderInfo = (
Order)userData;
999 var order = orderInfo.GetDismissal().WithManualPriority(character.GetCurrentOrder(orderInfo)?.ManualPriority ?? 0).WithOrderGiver(
Character.
Controlled);
1000 SetCharacterOrder(character, order);
1004 new GUIFrame(
new RectTransform(
new Point((
int)(1.5f * nodeWidth)), parent: nodeIcon.RectTransform,
Anchor.Center),
"OuterGlowCircular")
1006 CanBeFocused =
false,
1007 Color = order.
Color,
1013 if (hierarchyIndex != currentOrderIconList.Content.GetChildIndex(nodeIcon))
1015 nodeIcon.RectTransform.RepositionChildInHierarchy(hierarchyIndex);
1020 void RearrangeIcons()
1022 if (character.CurrentOrders !=
null)
1025 foreach (var currentOrderInfo
in character.CurrentOrders)
1027 var component = currentOrderIconList.Content.
FindChild(c => c?.UserData is
Order componentOrderInfo &&
1029 if (component ==
null) {
continue; }
1031 int newPriority = currentOrderInfo.ManualPriority;
1032 if (componentOrderInfo.ManualPriority != newPriority)
1034 component.UserData = componentOrderInfo.WithManualPriority(newPriority);
1038 currentOrderIconList.Content.RectTransform.SortChildren((x, y) =>
1040 var xOrder = (
Order)x.GUIComponent.UserData;
1041 var yOrder = (
Order)y.GUIComponent.UserData;
1042 return yOrder.ManualPriority.CompareTo(xOrder.ManualPriority);
1047 int iconCount = currentOrderIconList.Content.CountChildren;
1048 float nonScaledWidth = ((float)iconCount /
CharacterInfo.
MaxCurrentOrders) * parentGroup.Rect.Width + (iconCount * currentOrderIconList.Spacing);
1049 currentOrderIconList.RectTransform.NonScaledSize =
new Point((
int)nonScaledWidth, currentOrderIconList.RectTransform.NonScaledSize.Y);
1050 parentGroup.Recalculate();
1051 previousOrderIconGroup.Recalculate();
1059 if (orderInfo ==
null || orderInfo.
Identifier == dismissedOrderPrefab.Identifier) {
return; }
1061 var currentOrderIconList = GetCurrentOrderIconList(characterComponent);
1062 int maxPreviousOrderIcons = CharacterInfo.MaxCurrentOrders - currentOrderIconList.Content.CountChildren;
1064 if (maxPreviousOrderIcons < 1) {
return; }
1066 var previousOrderIconGroup = GetPreviousOrderIconGroup(characterComponent);
1067 if (previousOrderIconGroup.CountChildren >= maxPreviousOrderIcons)
1069 RemoveLastPreviousOrderIcon(previousOrderIconGroup);
1072 float nodeWidth = ((1.0f / CharacterInfo.MaxCurrentOrders) * previousOrderIconGroup.Parent.Rect.Width) - ((CharacterInfo.MaxCurrentOrders - 1) * currentOrderIconList.Spacing);
1073 Point size =
new Point((
int)nodeWidth, previousOrderIconGroup.Rect.Height);
1074 var previousOrderInfo = orderInfo.
WithType(
Order.OrderType.Previous);
1075 var prevOrderFrame =
new GUIButton(
new RectTransform(size, parent: previousOrderIconGroup.RectTransform), style:
null)
1077 UserData = previousOrderInfo,
1078 OnClicked = (button, userData) =>
1080 if (!CanIssueOrders) {
return false; }
1081 var orderInfo = (
Order)userData;
1082 int priority = GetManualOrderPriority(character, orderInfo);
1083 SetCharacterOrder(character, orderInfo.WithManualPriority(priority).WithOrderGiver(
Character.Controlled));
1086 OnSecondaryClicked = (button, userData) =>
1088 if (previousOrderIconGroup ==
null) {
return false; }
1089 previousOrderIconGroup.RemoveChild(button);
1090 previousOrderIconGroup.Recalculate();
1094 prevOrderFrame.RectTransform.IsFixedSize =
true;
1096 var prevOrderIconFrame =
new GUIFrame(
1097 new RectTransform(
new Vector2(0.8f), prevOrderFrame.RectTransform, anchor:
Anchor.BottomLeft),
1100 CreateNodeIcon(Vector2.One,
1101 prevOrderIconFrame.RectTransform,
1102 GetOrderIconSprite(previousOrderInfo),
1103 previousOrderInfo.Color,
1104 tooltip: CreateOrderTooltip(previousOrderInfo));
1106 foreach (GUIComponent c
in prevOrderIconFrame.Children)
1108 c.HoverColor = c.Color;
1109 c.PressedColor = c.Color;
1110 c.SelectedColor = c.Color;
1114 new RectTransform(
new Vector2(0.8f), prevOrderFrame.RectTransform, anchor:
Anchor.TopRight),
1118 CanBeFocused =
false
1121 prevOrderFrame.SetAsFirstChild();
1124 private void AddOldPreviousOrderIcons(Character character, GUIComponent oldCharacterComponent)
1126 var oldPrevOrderIcons = GetPreviousOrderIconGroup(oldCharacterComponent).Children;
1127 if (oldPrevOrderIcons.None()) {
return; }
1128 if (oldPrevOrderIcons.Count() > 1)
1130 oldPrevOrderIcons = oldPrevOrderIcons.Reverse();
1132 if (crewList.Content.Children.FirstOrDefault(c => c.UserData == character) is GUIComponent newCharacterComponent)
1134 foreach (GUIComponent icon
in oldPrevOrderIcons)
1136 if (icon.UserData is Order orderInfo)
1138 AddPreviousOrderIcon(character, newCharacterComponent, orderInfo);
1144 private void RemoveLastOrderIcon(GUIComponent characterComponent)
1146 var previousOrderIconGroup = GetPreviousOrderIconGroup(characterComponent);
1147 if (RemoveLastPreviousOrderIcon(previousOrderIconGroup))
1151 var currentOrderIconList = GetCurrentOrderIconList(characterComponent);
1152 if (currentOrderIconList.Content.CountChildren > 0)
1154 var iconToRemove = currentOrderIconList.Content.Children.Last();
1155 currentOrderIconList.RemoveChild(iconToRemove);
1160 private bool RemoveLastPreviousOrderIcon(GUILayoutGroup iconGroup)
1162 if (iconGroup.CountChildren > 0)
1164 var iconToRemove = iconGroup.Children.Last();
1165 iconGroup.RemoveChild(iconToRemove);
1171 private GUIListBox GetCurrentOrderIconList(GUIComponent characterComponent) =>
1172 characterComponent?.GetChild<GUILayoutGroup>().GetChild<GUILayoutGroup>().GetChild<GUIListBox>();
1174 private GUILayoutGroup GetPreviousOrderIconGroup(GUIComponent characterComponent) =>
1175 characterComponent?.GetChild<GUILayoutGroup>().GetChild<GUILayoutGroup>().GetChild<GUILayoutGroup>();
1177 private void OnOrdersRearranged(GUIListBox orderList,
object userData)
1179 var orderComponent = orderList.Content.GetChildByUserData(userData);
1180 if (orderComponent ==
null) {
return; }
1181 var orderInfo = (
Order)userData;
1182 var priority = Math.Max(CharacterInfo.HighestManualOrderPriority - orderList.Content.GetChildIndex(orderComponent), 1);
1183 if (orderInfo.ManualPriority == priority) {
return; }
1184 var character = (
Character)orderList.UserData;
1185 SetCharacterOrder(character, orderInfo.WithManualPriority(priority), isNewOrder:
false);
1188 private LocalizedString CreateOrderTooltip(OrderPrefab orderPrefab, Identifier option, Entity targetEntity)
1190 if (orderPrefab ==
null) {
return ""; }
1191 if (option != Identifier.Empty)
1193 return TextManager.GetWithVariables(
"crewlistordericontooltip".ToIdentifier(),
1194 (
"[ordername]".ToIdentifier(), orderPrefab.Name),
1195 (
"[orderoption]".ToIdentifier(), orderPrefab.GetOptionName(option)));
1197 else if (targetEntity is Item targetItem && targetItem.Prefab.MinimapIcon !=
null)
1199 return TextManager.GetWithVariables(
"crewlistordericontooltip".ToIdentifier(),
1200 (
"[ordername]".ToIdentifier(), orderPrefab.Name),
1201 (
"[orderoption]".ToIdentifier(), targetItem.Name));
1205 return orderPrefab.
Name;
1209 private LocalizedString CreateOrderTooltip(Order order)
1211 if (order.DisplayGiverInTooltip && order.OrderGiver !=
null)
1213 return TextManager.GetWithVariables(
"crewlistordericontooltip",
1214 (
"[ordername]", order.Name),
1215 (
"[orderoption]", order.OrderGiver.DisplayName));
1217 return CreateOrderTooltip(order.Prefab, order.Option, order?.TargetEntity);
1220 private Sprite GetOrderIconSprite(Order order)
1222 if (order ==
null) {
return null; }
1223 Sprite sprite =
null;
1224 if (order.Option != Identifier.Empty && order.Prefab.OptionSprites.Any())
1226 order.Prefab.OptionSprites.TryGetValue(order.Option, out sprite);
1228 if (sprite ==
null && order.TargetEntity is Item targetItem && targetItem.Prefab.MinimapIcon !=
null)
1230 sprite = targetItem.Prefab.MinimapIcon;
1232 return sprite ?? order.SymbolSprite;
1237 #region Updating and drawing the UI
1239 private void DrawMiniMapOverlay(SpriteBatch spriteBatch, GUICustomComponent container)
1243 if (sub?.HullVertices ==
null) {
return; }
1245 var dockedBorders = sub.GetDockedBorders();
1246 dockedBorders.Location += sub.WorldPosition.ToPoint();
1248 float scale = Math.Min(
1249 container.Rect.Width / (
float)dockedBorders.Width,
1250 container.Rect.Height / (
float)dockedBorders.Height) * 0.9f;
1252 float displayScale = ConvertUnits.ToDisplayUnits(scale);
1253 Vector2 offset = (sub.WorldPosition -
new Vector2(dockedBorders.Center.X, dockedBorders.Y - dockedBorders.Height / 2)) * scale;
1254 Vector2 center = container.Rect.Center.ToVector2();
1256 for (
int i = 0; i < sub.HullVertices.Count; i++)
1258 Vector2 start = (sub.HullVertices[i] * displayScale + offset);
1260 Vector2 end = (sub.HullVertices[(i + 1) % sub.HullVertices.Count] * displayScale + offset);
1262 GUI.DrawLine(spriteBatch, center + start, center + end, Color.DarkCyan * Rand.Range(0.3f, 0.35f), width: 10);
1268 if (GUI.DisableHUD) {
return; }
1269 if (CoroutineManager.IsCoroutineRunning(
"LevelTransition") || CoroutineManager.IsCoroutineRunning(
"SubmarineTransition")) {
return; }
1271 commandFrame?.AddToGUIUpdateList(order: 1);
1273 if (GUI.DisableUpperHUD) {
return; }
1277 var oldCrewList = crewList;
1278 InitProjectSpecific();
1282 if (!(oldCharacterComponent.
UserData is
Character character) || character.IsDead || character.Removed) {
continue; }
1283 AddCharacter(character);
1284 AddOldPreviousOrderIcons(character, oldCharacterComponent);
1296 if (crewList.Content.GetChild(TryAdjustIndex(1))?.UserData is
Character character)
1298 SelectCharacter(character);
1305 if (crewList.Content.GetChild(TryAdjustIndex(-1))?.UserData is
Character character)
1307 SelectCharacter(character);
1311 private void SelectCharacter(
Character character)
1314 if (!AllowCharacterSwitch) {
return; }
1317 var aiController =
Character.Controlled?.AIController;
1318 aiController?.Reset();
1321 HintManager.OnChangeCharacter();
1322 if (GameSession.TabMenuInstance !=
null && TabMenu.SelectedTab == TabMenu.InfoFrameTab.Talents)
1324 GameSession.TabMenuInstance.SelectInfoFrameTab(TabMenu.SelectedTab);
1328 private int TryAdjustIndex(
int amount)
1330 if (
Character.Controlled ==
null) {
return 0; }
1332 int currentIndex = crewList.Content.GetChildIndex(crewList.Content.GetChildByUserData(
Character.Controlled));
1333 if (currentIndex == -1) {
return 0; }
1335 int lastIndex = crewList.Content.CountChildren - 1;
1337 int index = currentIndex + amount;
1338 for (
int i = 0; i < crewList.Content.CountChildren; i++)
1340 if (index > lastIndex) { index = 0; }
1341 if (index < 0) { index = lastIndex; }
1343 if ((crewList.Content.GetChild(index)?.UserData as Character)?.IsOnPlayerTeam ??
false)
1354 private bool CreateOrder(OrderPrefab orderPrefab, Hull targetHull =
null)
1356 var sub =
Character.Controlled?.Submarine;
1358 if (sub ==
null || sub.TeamID !=
Character.Controlled.TeamID || sub.Info.IsWreck) {
return false; }
1360 var order =
new Order(orderPrefab, targetHull,
null,
Character.Controlled)
1361 .WithManualPriority(CharacterInfo.HighestManualOrderPriority);
1362 SetCharacterOrder(
null, order);
1366 HumanAIController.ReportProblem(
Character.Controlled, order);
1372 private void UpdateOrderDrag()
1374 if (DraggedOrderPrefab is { } orderPrefab)
1379 if (framesToSkip > 0)
1387 if (GUI.MouseOn is GUIFrame frame)
1389 if (frame.UserData is Hull data)
1393 else if (frame.Parent?.UserData is Hull parentData)
1401 DraggedOrderPrefab =
null;
1403 if (hull is
null && GUI.MouseOn is { Visible: true, CanBeFocused: true }) {
return; }
1405 hull ??= Hull.HullList.FirstOrDefault(h => h.WorldRect.ContainsWorld(Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition)));
1406 CreateOrder(orderPrefab, hull);
1411 DragOrder = DragOrder || Vector2.DistanceSquared(dragPoint, PlayerInput.MousePosition) > dragOrderTreshold * dragOrderTreshold;
1413 if (!PlayerInput.PrimaryMouseButtonHeld())
1421 DraggedOrderPrefab =
null;
1423 dragPoint = Vector2.Zero;
1430 partial
void UpdateProjectSpecific(
float deltaTime)
1433 if (GameMain.IsSingleplayer && GUI.KeyboardDispatcher.Subscriber ==
null)
1435 if (PlayerInput.KeyHit(
InputType.SelectNextCharacter))
1439 if (PlayerInput.KeyHit(
InputType.SelectPreviousCharacter))
1445 if (GUI.DisableHUD) {
return; }
1451 WasCommandInterfaceDisabledThisUpdate =
false;
1453 if (PlayerInput.KeyDown(
InputType.Command) &&
1454 (GUI.KeyboardDispatcher.Subscriber ==
null || (GUI.KeyboardDispatcher.Subscriber is GUIComponent component && (component == crewList || component.
IsChildOf(crewList)))) &&
1455 commandFrame ==
null && !clicklessSelectionActive && CanIssueOrders && !(GameMain.GameSession?.Campaign?.ShowCampaignUI ??
false) &&
1456 Character.Controlled?.SelectedItem?.Prefab is not { DisableCommandMenuWhenSelected: true } &&
1457 !Inventory.IsMouseOnInventory)
1459 if (PlayerInput.KeyDown(
InputType.ContextualCommand))
1461 CreateCommandUI(FindEntityContext(),
true);
1465 CreateCommandUI(
CharacterHUD.MouseOnCharacterPortrait() ?
Character.Controlled : GUI.MouseOn?.UserData as Character);
1468 clicklessSelectionActive = isOpeningClick =
true;
1471 if (commandFrame !=
null)
1473 void ResetNodeSelection(GUIButton newSelectedNode =
null)
1475 if (commandFrame ==
null) {
return; }
1476 selectedNode?.Children.ForEach(c => c.Color = c.HoverColor * nodeColorMultiplier);
1477 selectedNode = newSelectedNode;
1479 isSelectionHighlighted =
false;
1484 bool isMouseOnOptionNode = optionNodes.Any(n => GUI.IsMouseOn(n.Button));
1485 bool isMouseOnShortcutNode = !isMouseOnOptionNode && shortcutNodes.Any(n => GUI.IsMouseOn(n));
1486 bool hitDeselect = PlayerInput.KeyHit(
InputType.Deselect) &&
1487 (!PlayerInput.SecondaryMouseButtonClicked() || (!isMouseOnOptionNode && !isMouseOnShortcutNode));
1489 bool isBoundToPrimaryMouse = GameSettings.CurrentConfig.KeyMap.Bindings[
InputType.Command].MouseButton ==
MouseButton.PrimaryMouse;
1490 bool canToggleInterface = !isBoundToPrimaryMouse ||
1491 (!isMouseOnOptionNode && !isMouseOnShortcutNode && extraOptionNodes.None(n => GUI.IsMouseOn(n)) && !GUI.IsMouseOn(returnNode));
1494 if (hitDeselect || PlayerInput.KeyHit(Keys.Escape) || !CanIssueOrders ||
1495 (canToggleInterface && PlayerInput.KeyHit(
InputType.Command) && selectedNode ==
null && !clicklessSelectionActive))
1499 else if (PlayerInput.KeyUp(
InputType.Command))
1502 if (canToggleInterface && !isOpeningClick && clicklessSelectionActive && timeSelected < 0.15f)
1508 clicklessSelectionActive = isOpeningClick =
false;
1509 if (selectedNode !=
null)
1511 ResetNodeSelection();
1515 else if (PlayerInput.KeyDown(
InputType.Command) && (targetFrame ==
null || !targetFrame.Visible))
1518 if (!GUI.IsMouseOn(centerNode))
1520 clicklessSelectionActive =
true;
1522 var mouseBearing = GetBearing(centerNode.Center, PlayerInput.MousePosition, flipY:
true);
1524 GUIComponent closestNode =
null;
1525 float closestBearing = 0;
1527 optionNodes.ForEach(n => CheckIfClosest(n.Button));
1528 CheckIfClosest(returnNode);
1530 void CheckIfClosest(GUIComponent comp)
1532 if (comp ==
null) {
return; }
1533 var offset = comp.RectTransform.AbsoluteOffset;
1534 var nodeBearing = GetBearing(centerNode.RectTransform.AbsoluteOffset.ToVector2(), offset.ToVector2(), flipY:
true);
1535 if (closestNode ==
null)
1538 closestBearing = Math.Abs(nodeBearing - mouseBearing);
1542 var difference = Math.Abs(nodeBearing - mouseBearing);
1543 if (difference < closestBearing)
1546 closestBearing = difference;
1551 if (closestNode !=
null && closestNode.CanBeFocused && closestNode == selectedNode)
1553 timeSelected += deltaTime;
1554 if (timeSelected >= selectionTime)
1556 if (PlayerInput.IsShiftDown() && selectedNode.OnSecondaryClicked !=
null)
1558 selectedNode.OnSecondaryClicked.Invoke(selectedNode, selectedNode.UserData);
1562 selectedNode.OnClicked?.Invoke(selectedNode, selectedNode.UserData);
1564 ResetNodeSelection();
1566 else if (timeSelected >= 0.15f && !isSelectionHighlighted)
1568 selectedNode.Children.ForEach(c => c.Color = c.HoverColor);
1569 isSelectionHighlighted =
true;
1574 ResetNodeSelection(closestNode as GUIButton);
1577 else if (selectedNode !=
null)
1579 ResetNodeSelection();
1583 var hotkeyHit =
false;
1584 foreach (OptionNode node
in optionNodes)
1586 if (node.Keys != Keys.None && PlayerInput.KeyHit(node.Keys))
1588 var button = node.Button;
1589 if (PlayerInput.IsShiftDown() && button?.OnSecondaryClicked !=
null)
1591 button.OnSecondaryClicked.Invoke(button, button.UserData);
1595 button?.OnClicked?.Invoke(button, button.UserData);
1597 ResetNodeSelection();
1605 if (returnNodeHotkey != Keys.None && PlayerInput.KeyHit(returnNodeHotkey))
1607 returnNode?.OnClicked?.Invoke(returnNode, returnNode.UserData);
1608 ResetNodeSelection();
1610 else if (expandNodeHotkey != Keys.None && PlayerInput.KeyHit(expandNodeHotkey))
1612 expandNode?.OnClicked?.Invoke(expandNode, expandNode.UserData);
1613 ResetNodeSelection();
1617 else if (!PlayerInput.KeyDown(
InputType.Command))
1619 clicklessSelectionActive =
false;
1624 if (ChatBox !=
null)
1628 if (!DebugConsole.IsOpen &&
ChatBox.InputBox.Visible && GUI.KeyboardDispatcher.Subscriber ==
null && !
ChatBox.InputBox.Selected)
1630 ChatBox.ApplySelectionInputs();
1634 if (!GUI.DisableUpperHUD)
1636 crewArea.
Visible = characters.Count > 0 && CharacterHealth.OpenHealthWindow ==
null;
1638 foreach (GUIComponent characterComponent
in crewList.Content.Children)
1640 if (characterComponent.UserData is Character character)
1642 if (character.Removed)
1644 characterComponent.Visible =
false;
1648 characterComponent.Visible =
Character.Controlled ==
null ||
Character.Controlled.TeamID == character.TeamID;
1650 (character.CurrentHull ==
Character.Controlled.CurrentHull || Vector2.DistanceSquared(
Character.Controlled.WorldPosition, character.WorldPosition) < 500.0f * 500.0f))
1652 characterComponent.Visible =
true;
1654 if (characterComponent.Visible)
1656 if (character ==
Character.Controlled && crewList.SelectedComponent != characterComponent)
1658 crewList.Select(character, GUIListBox.Force.Yes);
1661 if (GetCurrentOrderIconList(characterComponent) is GUIListBox currentOrderIconList)
1663 foreach (var orderIcon
in currentOrderIconList.Content.Children)
1665 if (orderIcon.UserData is not Order order) {
continue; }
1666 if (order.ColoredWhenControllingGiver && order.OrderGiver !=
Character.Controlled)
1668 orderIcon.Color = AIObjective.ObjectiveIconColor;
1672 orderIcon.Color = order.Color;
1678 if (GameMain.IsSingleplayer && character.IsBot && character.AIController is HumanAIController controller &&
1679 controller.ObjectiveManager is AIObjectiveManager objectiveManager)
1681 if (objectiveManager.CurrentObjective is AIObjective currentObjective)
1683 if (objectiveManager.IsOrder(currentObjective))
1685 var orderInfo = objectiveManager.CurrentOrders.FirstOrDefault(o => o.Objective == currentObjective);
1686 if (orderInfo !=
null)
1688 SetOrderHighlight(characterComponent, orderInfo.Identifier, orderInfo.Option);
1693 CreateObjectiveIcon(characterComponent, currentObjective);
1698 if (character.IsPlayer)
1700 DisableOrderHighlight(characterComponent);
1701 RemoveObjectiveIcon(characterComponent);
1703 if (GetSoundIconParent(characterComponent) is GUIComponent soundIconParent)
1705 if (soundIconParent.FindChild(c => c.UserData is Pair<string, float> pair && pair.First ==
"soundicon") is GUIImage soundIcon)
1707 if (character.IsPlayer)
1709 soundIconParent.Visible =
true;
1712 else if(soundIcon.Visible)
1714 var userdata = soundIcon.UserData as Pair<string, float>;
1715 userdata.Second = 0.0f;
1716 soundIconParent.Visible = soundIcon.Visible =
false;
1724 traitorButtons.ForEach(btn => btn.Visible =
Character.Controlled is { IsDead: false } && btn.UserData as Character !=
Character.Controlled);
1727 new Vector2(-crewArea.
Rect.Width - HUDLayoutSettings.Padding, 0.0f),
1729 crewListOpenState).ToPoint();
1731 crewListOpenState = IsCrewMenuOpen ?
1732 Math.Min(crewListOpenState + deltaTime * 2.0f, 1.0f) :
1733 Math.Max(crewListOpenState - deltaTime * 2.0f, 0.0f);
1735 if (GUI.KeyboardDispatcher.Subscriber ==
null && PlayerInput.KeyHit(
InputType.CrewOrders))
1738 IsCrewMenuOpen = !IsCrewMenuOpen;
1745 private void SetOrderHighlight(GUIComponent characterComponent, Identifier orderIdentifier, Identifier orderOption)
1747 if (characterComponent ==
null) {
return; }
1748 RemoveObjectiveIcon(characterComponent);
1749 if (GetCurrentOrderIconList(characterComponent) is GUIListBox currentOrderIconList)
1751 bool foundMatch =
false;
1752 foreach (var orderIcon
in currentOrderIconList.Content.Children)
1754 if (orderIcon.GetChildByUserData(
"glow") is not GUIComponent glowComponent) {
continue; }
1755 glowComponent.Color = orderIcon.Color;
1758 glowComponent.Visible =
false;
1761 var orderInfo = (
Order)orderIcon.UserData;
1762 foundMatch = orderInfo.MatchesOrder(orderIdentifier, orderOption);
1763 glowComponent.Visible = foundMatch;
1770 if (crewList ==
null) {
return; }
1771 var characterComponent = crewList.Content.GetChildByUserData(character);
1772 SetOrderHighlight(characterComponent, orderIdentifier, orderOption);
1775 private void DisableOrderHighlight(
GUIComponent characterComponent)
1777 if (GetCurrentOrderIconList(characterComponent) is
GUIListBox currentOrderIconList)
1779 foreach (var orderIcon
in currentOrderIconList.Content.Children)
1781 var glowComponent = orderIcon.GetChildByUserData(
"glow");
1782 if (glowComponent ==
null) {
continue; }
1783 glowComponent.Visible =
false;
1788 private void CreateObjectiveIcon(GUIComponent characterComponent, Sprite sprite, LocalizedString tooltip)
1790 if (characterComponent ==
null || !(characterComponent.UserData is Character character) || character.IsPlayer) {
return; }
1791 DisableOrderHighlight(characterComponent);
1792 if (GetObjectiveIconParent(characterComponent) is GUIFrame objectiveIconFrame)
1794 var existingObjectiveIcon = objectiveIconFrame.GetChild<GUIImage>();
1795 if (existingObjectiveIcon ==
null || existingObjectiveIcon.Sprite != sprite || existingObjectiveIcon.ToolTip != tooltip)
1797 objectiveIconFrame.ClearChildren();
1800 var objectiveIcon = CreateNodeIcon(Vector2.One, objectiveIconFrame.RectTransform, sprite, AIObjective.ObjectiveIconColor, tooltip: tooltip);
1801 new GUIFrame(
new RectTransform(
new Vector2(1.5f), objectiveIcon.RectTransform, anchor:
Anchor.Center), style:
"OuterGlowCircular")
1803 CanBeFocused =
false,
1804 Color = AIObjective.ObjectiveIconColor
1806 objectiveIconFrame.Visible =
true;
1810 objectiveIconFrame.Visible =
false;
1818 CreateObjectiveIcon(crewList?.Content.GetChildByUserData(character),
1820 GetObjectiveIconTooltip(identifier, option, targetEntity));
1825 CreateObjectiveIcon(characterComponent,
1826 objective?.GetSprite(),
1827 GetObjectiveIconTooltip(objective));
1830 private LocalizedString GetObjectiveIconTooltip(Identifier identifier, Identifier option, Entity targetEntity)
1832 LocalizedString variableValue;
1833 if (OrderPrefab.Prefabs.ContainsKey(identifier))
1835 var orderPrefab = OrderPrefab.Prefabs[identifier];
1836 variableValue = CreateOrderTooltip(orderPrefab, option, targetEntity);
1840 variableValue = TextManager.Get($
"objective.{identifier}");
1842 return variableValue.IsNullOrEmpty() ? variableValue : TextManager.GetWithVariable(
"crewlistobjectivetooltip",
"[objective]", variableValue);
1845 private LocalizedString GetObjectiveIconTooltip(AIObjective objective)
1847 return objective ==
null ?
"" :
1848 GetObjectiveIconTooltip(objective.Identifier, objective.Option, (objective as AIObjectiveOperateItem)?.OperateTarget);
1851 private GUIComponent GetObjectiveIconParent(GUIComponent characterComponent)
1853 return characterComponent?
1854 .GetChild<GUILayoutGroup>()?
1855 .GetChildByUserData(
"extraicons")?
1856 .GetChildByUserData(
"objectiveicon");
1859 private void RemoveObjectiveIcon(GUIComponent characterComponent)
1861 if (GetObjectiveIconParent(characterComponent) is GUIFrame objectiveIconFrame)
1863 objectiveIconFrame.ClearChildren();
1864 objectiveIconFrame.Visible =
false;
1872 public static bool IsCommandInterfaceOpen
1886 private GUIFrame commandFrame, targetFrame;
1887 private GUIButton centerNode, returnNode, expandNode;
1888 private GUIFrame shortcutCenterNode;
1889 private class OptionNode
1892 public readonly Keys Keys;
1893 public OptionNode(
GUIButton guiComponent, Keys keys)
1895 Button = guiComponent;
1899 private readonly List<OptionNode> optionNodes =
new List<OptionNode>();
1900 private Keys returnNodeHotkey = Keys.None, expandNodeHotkey = Keys.None;
1901 private readonly List<GUIComponent> shortcutNodes =
new List<GUIComponent>();
1902 private readonly List<GUIComponent> extraOptionNodes =
new List<GUIComponent>();
1903 private GUICustomComponent nodeConnectors;
1904 private GUIImage background;
1906 private GUIButton selectedNode;
1907 private readonly
float selectionTime = 0.75f;
1908 private float timeSelected = 0.0f;
1909 private bool clicklessSelectionActive, isOpeningClick, isSelectionHighlighted;
1911 private Point centerNodeSize, nodeSize, shortcutCenterNodeSize, shortcutNodeSize, returnNodeSize, assignmentNodeSize;
1912 private float centerNodeMargin, optionNodeMargin, shortcutCenterNodeMargin, shortcutNodeMargin, returnNodeMargin;
1914 private List<OrderCategory> availableCategories;
1915 private Stack<GUIButton> historyNodes =
new Stack<GUIButton>();
1916 private readonly List<Character> extraOptionCharacters =
new List<Character>();
1921 private const float nodeColorMultiplier = 0.75f;
1922 private int nodeDistance = (int)(GUI.Scale * 250);
1923 private const float returnNodeDistanceModifier = 0.65f;
1924 private OrderPrefab dismissedOrderPrefab => OrderPrefab.Dismissal;
1926 private Item itemContext;
1927 private Hull hullContext;
1928 private WallSection wallContext;
1929 private bool isContextual;
1930 private readonly List<Order> contextualOrders =
new List<Order>();
1931 private Point shorcutCenterNodeOffset;
1932 private const int maxShortcutNodeCount = 4;
1934 private bool WasCommandInterfaceDisabledThisUpdate {
get;
set; }
1935 public static bool CanIssueOrders
1947 private bool CanCharacterBeHeard()
1952 if (Character.Controlled !=
null)
1954 if (characterContext ==
null)
1956 return characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled)) || GetOrderableFriendlyNPCs().Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled));
1960 return characterContext.CanHearCharacter(
Character.Controlled);
1966 private Entity FindEntityContext()
1968 if (
Character.Controlled?.FocusedCharacter is Character focusedCharacter && !focusedCharacter.IsDead &&
1969 HumanAIController.IsFriendly(
Character.Controlled, focusedCharacter) &&
Character.Controlled.TeamID == focusedCharacter.TeamID)
1971 if (
Character.Controlled?.FocusedItem !=
null)
1973 Vector2 mousePos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
1974 if (Vector2.Distance(mousePos, focusedCharacter.WorldPosition) < Vector2.Distance(mousePos,
Character.Controlled.FocusedItem.WorldPosition))
1976 return focusedCharacter;
1980 return Character.Controlled.FocusedItem;
1985 return focusedCharacter;
1989 else if (TryGetBreachedHullAtHoveredWall(out Hull breachedHull, out wallContext))
1991 return breachedHull;
1995 return Character.Controlled?.FocusedItem;
2001 CreateCommandUI(entityContext, forceContextual);
2003 clicklessSelectionActive = isOpeningClick =
true;
2006 private void CreateCommandUI(
Entity entityContext =
null,
bool forceContextual =
false)
2008 if (commandFrame !=
null) { DisableCommandUI(); }
2012 isContextual = forceContextual;
2013 if (entityContext is Character character)
2015 characterContext = character;
2019 isContextual =
false;
2021 else if (entityContext is Item item)
2024 characterContext =
null;
2027 isContextual =
true;
2029 else if (entityContext is Hull hull)
2032 characterContext =
null;
2034 isContextual =
true;
2039 commandFrame =
new GUIFrame(
2040 new RectTransform(Vector2.One, GUI.Canvas, anchor:
Anchor.Center),
2042 color: Color.Transparent);
2043 background =
new GUIImage(
2045 "CommandBackground");
2046 background.Color = background.Color * 0.8f;
2047 GUIButton startNode =
null;
2048 if (characterContext ==
null)
2050 startNode =
new GUIButton(
2051 new RectTransform(centerNodeSize, parent: commandFrame.
RectTransform, anchor:
Anchor.Center),
2053 CreateNodeIcon(startNode.RectTransform,
"CommandStartNode");
2058 startNode =
new GUIButton(
2059 new RectTransform(centerNodeSize, parent: commandFrame.
RectTransform, anchor:
Anchor.Center),
2063 new RectTransform(Vector2.One, startNode.RectTransform, anchor:
Anchor.Center),
2064 "CommandNodeContainer",
2067 Color = characterContext.Info?.Job?.Prefab !=
null ? characterContext.Info.Job.Prefab.UIColor * nodeColorMultiplier : Color.White,
2068 HoverColor = characterContext.Info?.Job?.Prefab !=
null ? characterContext.Info.Job.Prefab.UIColor : Color.White,
2069 UserData =
"colorsource"
2072 var characterIcon =
new GUICustomComponent(
2073 new RectTransform(Vector2.One, startNode.RectTransform, anchor:
Anchor.Center),
2076 if (entityContext is not Character character || character?.Info == null) { return; }
2077 var node = startNode;
2078 character.Info.DrawJobIcon(spriteBatch,
2079 new Rectangle((
int)(node.Rect.X + node.Rect.Width * 0.5f), (
int)(node.Rect.Y + node.Rect.Height * 0.1f), (
int)(node.Rect.Width * 0.6f), (
int)(node.Rect.Height * 0.8f)));
2080 character.Info.DrawIcon(spriteBatch,
new Vector2(node.Rect.X + node.Rect.Width * 0.35f, node.Center.Y), node.Rect.Size.ToVector2() * 0.7f);
2082 SetCharacterTooltip(characterIcon, entityContext as Character);
2084 SetCenterNode(startNode);
2086 availableCategories ??= GetAvailableCategories();
2090 CreateContextualOrderNodes();
2094 CreateShortcutNodes();
2095 CreateOrderCategoryNodes();
2098 CreateNodeConnectors();
2101 Character.Controlled.dontFollowCursor =
true;
2104 HintManager.OnShowCommandInterface();
2109 if (commandFrame ==
null)
2122 private void ScaleCommandUI()
2125 nodeSize =
new Point((
int)(100 * GUI.Scale));
2126 centerNodeSize = nodeSize;
2127 returnNodeSize =
new Point((
int)(48 * GUI.Scale));
2128 assignmentNodeSize =
new Point((
int)(64 * GUI.Scale));
2129 shortcutCenterNodeSize = returnNodeSize;
2130 shortcutNodeSize = assignmentNodeSize;
2133 centerNodeMargin = centerNodeSize.X * 0.5f;
2134 optionNodeMargin = nodeSize.X * 0.5f;
2135 shortcutCenterNodeMargin = shortcutCenterNodeSize.X * 0.45f;
2136 shortcutNodeMargin = shortcutNodeSize.X * 0.5f;
2137 returnNodeMargin = returnNodeSize.X * 0.5f;
2139 nodeDistance = (int)(150 * GUI.Scale);
2140 shorcutCenterNodeOffset =
new Point(0, (
int)(1.35f * nodeDistance));
2143 private List<OrderCategory> GetAvailableCategories()
2145 availableCategories =
new List<OrderCategory>();
2146 foreach (OrderCategory category
in Enum.GetValues(typeof(OrderCategory)))
2148 if (OrderPrefab.Prefabs.Any(o => o.Category == category && !o.IsReport))
2150 availableCategories.Add(category);
2153 return availableCategories;
2156 private void CreateNodeConnectors()
2158 nodeConnectors =
new GUICustomComponent(
2160 onDraw: DrawNodeConnectors)
2162 CanBeFocused =
false
2164 nodeConnectors.SetAsFirstChild();
2165 background.SetAsFirstChild();
2168 private void DrawNodeConnectors(SpriteBatch spriteBatch, GUIComponent container)
2170 if (centerNode ==
null || optionNodes ==
null) {
return; }
2171 var startNodePos = centerNode.
Rect.Center.ToVector2();
2173 if (optionNodes.FirstOrDefault()?.Button.UserData is not Character)
2176 if (targetFrame ==
null || !targetFrame.
Visible)
2178 optionNodes.ForEach(n => DrawNodeConnector(startNodePos, centerNodeMargin, n.Button, optionNodeMargin, spriteBatch));
2183 foreach (var node
in optionNodes)
2185 float iconRadius = 0.5f * optionNodeMargin;
2186 Vector2 itemPosition = node.Button.Parent.Rect.Center.ToVector2();
2187 if (Vector2.Distance(node.Button.Center, itemPosition) <= iconRadius) {
continue; }
2188 DrawNodeConnector(itemPosition, 0.0f, node.Button, iconRadius, spriteBatch, widthMultiplier: 0.5f);
2189 GUI.DrawFilledRectangle(spriteBatch, itemPosition - Vector2.One,
new Vector2(3),
2190 node.Button.GetChildByUserData(
"colorsource")?.Color ?? Color.White);
2194 DrawNodeConnector(startNodePos, centerNodeMargin, returnNode, returnNodeMargin, spriteBatch);
2195 if (shortcutCenterNode ==
null || !shortcutCenterNode.
Visible) {
return; }
2196 DrawNodeConnector(startNodePos, centerNodeMargin, shortcutCenterNode, shortcutCenterNodeMargin, spriteBatch);
2197 startNodePos = shortcutCenterNode.
Rect.Center.ToVector2();
2198 shortcutNodes.ForEach(n => DrawNodeConnector(startNodePos, shortcutCenterNodeMargin, n, shortcutNodeMargin, spriteBatch));
2201 private void DrawNodeConnector(Vector2 startNodePos,
float startNodeMargin, GUIComponent endNode,
float endNodeMargin, SpriteBatch spriteBatch,
float widthMultiplier = 1.0f)
2203 if (endNode ==
null || !endNode.Visible) {
return; }
2204 var endNodePos = endNode.Rect.Center.ToVector2();
2205 var direction = (endNodePos - startNodePos) / Vector2.Distance(startNodePos, endNodePos);
2206 var start = startNodePos + direction * startNodeMargin;
2207 var end = endNodePos - direction * endNodeMargin;
2208 var colorSource = endNode.GetChildByUserData(
"colorsource");
2209 if ((selectedNode ==
null && endNode != shortcutCenterNode && GUI.IsMouseOn(endNode)) ||
2210 (isSelectionHighlighted && (endNode == selectedNode || (endNode == shortcutCenterNode && shortcutNodes.Any(n => GUI.IsMouseOn(n))))))
2212 GUI.DrawLine(spriteBatch, start, end, colorSource?.HoverColor ?? Color.White, width: Math.Max(widthMultiplier * 4.0f, 1.0f));
2216 GUI.DrawLine(spriteBatch, start, end, colorSource?.Color ?? Color.White * nodeColorMultiplier, width: Math.Max(widthMultiplier * 2.0f, 1.0f));
2222 if (commandFrame ==
null) {
return; }
2223 WasCommandInterfaceDisabledThisUpdate =
true;
2224 RemoveOptionNodes();
2225 historyNodes.Clear();
2226 nodeConnectors =
null;
2230 shortcutCenterNode =
null;
2232 selectedNode =
null;
2235 commandFrame =
null;
2236 extraOptionCharacters.Clear();
2237 isOpeningClick = isSelectionHighlighted =
false;
2238 characterContext =
null;
2240 isContextual =
false;
2241 contextualOrders.Clear();
2242 returnNodeHotkey = expandNodeHotkey = Keys.None;
2249 private bool NavigateForward(
GUIButton node,
object userData)
2251 if (commandFrame ==
null) {
return false; }
2252 if (optionNodes.Find(n => n.Button == node) is not OptionNode optionNode || !optionNodes.Remove(optionNode))
2254 shortcutNodes.Remove(node);
2256 RemoveOptionNodes();
2257 bool wasMinimapVisible = targetFrame !=
null && targetFrame.
Visible;
2260 if (returnNode !=
null)
2263 returnNode.
Children.ForEach(child => child.Visible =
false);
2265 historyNodes.Push(returnNode);
2269 bool placeReturnNodeOnTheBottom = wasMinimapVisible ||
2270 (node?.
UserData is
Order order && order.GetMatchingItems(
true, interactableFor: characterContext ??
Character.Controlled).Count > 1);
2271 var offset = placeReturnNodeOnTheBottom ?
2272 new Point(0, (
int)(returnNodeDistanceModifier * nodeDistance)) :
2273 node.RectTransform.AbsoluteOffset.Multiply(-returnNodeDistanceModifier);
2274 SetReturnNode(centerNode, offset);
2276 SetCenterNode(node);
2277 if (shortcutCenterNode !=
null)
2280 shortcutCenterNode =
null;
2283 CreateNodes(userData);
2284 CreateReturnNodeHotkey();
2288 private bool NavigateBackward(GUIButton node,
object userData)
2290 if (commandFrame ==
null) {
return false; }
2291 RemoveOptionNodes();
2295 SetCenterNode(node);
2296 if (historyNodes.Count > 0)
2298 var historyNode = historyNodes.Pop();
2299 SetReturnNode(historyNode, historyNode.RectTransform.AbsoluteOffset);
2300 historyNode.Visible =
true;
2301 historyNode.RemoveChild(historyNode.GetChildByUserData(
"hotkey"));
2302 historyNode.Children.ForEach(child => child.Visible =
true);
2308 CreateNodes(userData);
2309 CreateReturnNodeHotkey();
2313 private void HideMinimap()
2315 if (targetFrame ==
null || !targetFrame.
Visible) {
return; }
2318 nodeConnectors.RectTransform.Parent = commandFrame.
RectTransform;
2322 private void CreateReturnNodeHotkey()
2324 if (returnNode !=
null && returnNode.
Visible)
2327 if (targetFrame ==
null || !targetFrame.
Visible)
2329 hotkey = optionNodes.Count + 1;
2330 if (expandNode !=
null && expandNode.
Visible) { hotkey += 1; }
2332 CreateHotkeyIcon(returnNode.
RectTransform, hotkey % 10,
true);
2333 returnNodeHotkey = Keys.D0 + hotkey % 10;
2337 returnNodeHotkey = Keys.None;
2341 private void SetCenterNode(GUIButton node,
bool resetAnchor =
false)
2348 node.RectTransform.SetPosition(
Anchor.Center);
2349 node.RectTransform.MoveOverTime(Point.Zero, CommandNodeAnimDuration);
2350 node.RectTransform.ScaleOverTime(centerNodeSize, CommandNodeAnimDuration);
2351 node.RemoveChild(node.GetChildByUserData(
"hotkey"));
2352 foreach (GUIComponent c
in node.Children)
2354 c.Color = c.HoverColor * nodeColorMultiplier;
2355 c.HoverColor = c.Color;
2356 c.PressedColor = c.Color;
2357 c.SelectedColor = c.Color;
2358 SetCharacterTooltip(c, characterContext);
2360 node.OnClicked =
null;
2361 node.OnSecondaryClicked =
null;
2362 node.CanBeFocused =
false;
2366 private void SetReturnNode(GUIButton node, Point offset)
2369 node.RectTransform.ScaleOverTime(returnNodeSize, CommandNodeAnimDuration);
2370 foreach (GUIComponent c
in node.Children)
2372 c.HoverColor = c.Color * (1 / nodeColorMultiplier);
2373 c.PressedColor = c.HoverColor;
2374 c.SelectedColor = c.HoverColor;
2375 c.ToolTip = TextManager.Get(
"commandui.return");
2377 node.OnClicked = NavigateBackward;
2378 node.OnSecondaryClicked =
null;
2379 node.CanBeFocused =
true;
2383 private bool CreateNodes(
object userData)
2385 if (userData ==
null)
2389 CreateContextualOrderNodes();
2393 CreateShortcutNodes();
2394 CreateOrderCategoryNodes();
2397 else if (userData is OrderCategory category)
2399 CreateOrderNodes(category);
2401 else if (userData is Order nodeOrder)
2403 Submarine submarine = GetTargetSubmarine();
2404 List<Item> matchingItems =
null;
2405 if (itemContext ==
null && nodeOrder.MustSetTarget)
2407 matchingItems = nodeOrder.GetMatchingItems(submarine,
true, interactableFor: characterContext ??
Character.Controlled);
2410 if (itemContext ==
null && nodeOrder.TargetEntity is not Item && matchingItems !=
null && matchingItems.Count > 1)
2412 CreateMinimapNodes(nodeOrder, submarine, matchingItems);
2417 CreateOrderOptionNodes(nodeOrder, itemContext ?? nodeOrder.TargetEntity as Item ?? matchingItems?.FirstOrDefault());
2420 else if (userData is MinimapNodeData {
Order: { } minimapOrder} && minimapOrder.Prefab.HasOptions)
2422 CreateOrderOptionNodes(minimapOrder, minimapOrder.TargetEntity as Item);
2426 DebugConsole.ThrowError($
"Unexpected node user data of type {userData.GetType()} when creating command interface nodes");
2432 private void RemoveOptionNodes()
2434 if (commandFrame !=
null)
2436 optionNodes.ForEach(node => commandFrame.
RemoveChild(node.Button));
2437 shortcutNodes.ForEach(node => commandFrame.
RemoveChild(node));
2440 optionNodes.Clear();
2441 shortcutNodes.Clear();
2443 expandNodeHotkey = Keys.None;
2444 RemoveExtraOptionNodes();
2447 private void RemoveExtraOptionNodes()
2449 if (commandFrame !=
null)
2451 extraOptionNodes.ForEach(node => commandFrame.
RemoveChild(node));
2453 extraOptionNodes.Clear();
2456 private void CreateOrderCategoryNodes()
2459 var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, availableCategories.Count, MathHelper.ToRadians(225));
2460 var offsetIndex = 0;
2461 availableCategories.ForEach(oc => CreateOrderCategoryNode(oc, offsets[offsetIndex++].ToPoint(), offsetIndex));
2464 private void CreateOrderCategoryNode(OrderCategory category, Point offset,
int hotkey)
2466 var node =
new GUIButton(
2467 new RectTransform(nodeSize, parent: commandFrame.
RectTransform, anchor:
Anchor.Center), style:
null)
2469 UserData = category,
2470 OnClicked = NavigateForward
2473 node.RectTransform.MoveOverTime(offset, CommandNodeAnimDuration);
2474 var icon = OrderCategoryIcon.OrderCategoryIcons.FirstOrDefault(ic => ic.Category == category);
2475 if (icon is not
null)
2477 var tooltip = TextManager.Get($
"ordercategorytitle.{category}");
2478 var categoryDescription = TextManager.Get($
"ordercategorydescription.{category}");
2479 if (!categoryDescription.IsNullOrWhiteSpace()) { tooltip +=
"\n" + categoryDescription; }
2480 CreateNodeIcon(Vector2.One, node.RectTransform, icon.Sprite, icon.Color, tooltip: tooltip);
2482 CreateHotkeyIcon(node.RectTransform, hotkey % 10);
2483 optionNodes.Add(
new OptionNode(node, Keys.D0 + hotkey % 10));
2489 private void CreateShortcutNodes()
2491 if (!(GetTargetSubmarine() is { } sub)) {
return; }
2492 shortcutNodes.Clear();
2493 var subItems = sub.GetItems(
false);
2494 if (CanFitMoreNodes() && subItems.Find(i => i.HasTag(Tags.Reactor) && i.IsPlayerTeamInteractable)?.GetComponent<
Reactor>() is
Reactor reactor)
2496 float reactorOutput = -reactor.CurrPowerConsumption;
2499 if (ShouldDelegateOrder(
"operatereactor") && reactorOutput <
float.Epsilon && characters.None(c => c.SelectedItem == reactor.Item))
2501 var orderPrefab = OrderPrefab.Prefabs[
"operatereactor"];
2502 var order =
new Order(orderPrefab, orderPrefab.Options[0], reactor.Item, reactor);
2503 if (IsNonDuplicateOrder(order))
2505 AddOrderNode(order);
2512 if (CanFitMoreNodes() && ShouldDelegateOrder(
"steer") && IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs[
"steer"]) &&
2513 subItems.Find(i => i.HasTag(Tags.NavTerminal) && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedItem == nav) &&
2516 var order =
new Order(OrderPrefab.Prefabs[
"steer"], steering.Item, steering);
2517 AddOrderNode(order);
2521 if (CanFitMoreNodes() && ShouldDelegateOrder(
"fightintruders") &&
2522 ActiveOrders.Any(o => o.Order.Identifier ==
"reportintruders") &&
2523 IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs[
"fightintruders"]))
2525 AddOrderNodeWithIdentifier(
"fightintruders");
2529 if (CanFitMoreNodes() && ShouldDelegateOrder(
"fixleaks") &&
2530 IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs[
"fixleaks"]) &&
2531 ActiveOrders.Any(o => o.Order.Identifier ==
"reportbreach"))
2533 AddOrderNodeWithIdentifier(
"fixleaks");
2536 if (CanFitMoreNodes() && ActiveOrders.Any(o => o.Order.Identifier ==
"reportbrokendevices"))
2538 var reportBrokenDevices = OrderPrefab.Prefabs[
"reportbrokendevices"];
2540 bool useSpecificRepairOrder =
false;
2541 if (CanFitMoreNodes() && ShouldDelegateOrder(
"repairelectrical") &&
2542 ActiveOrders.Any(o => o.Order.Prefab == reportBrokenDevices && o.Order.TargetItemComponent is
Repairable r && r.
RequiredSkills.Any(s => s.Identifier ==
"electrical")))
2544 if (IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs[
"repairelectrical"]))
2546 AddOrderNodeWithIdentifier(
"repairelectrical");
2548 useSpecificRepairOrder =
true;
2550 if (CanFitMoreNodes() && ShouldDelegateOrder(
"repairmechanical") &&
2551 ActiveOrders.Any(o => o.Order.Prefab == reportBrokenDevices && o.Order.TargetItemComponent is
Repairable r && r.
RequiredSkills.Any(s => s.Identifier ==
"mechanical")))
2553 if (IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs[
"repairmechanical"]))
2555 AddOrderNodeWithIdentifier(
"repairmechanical");
2557 useSpecificRepairOrder =
true;
2559 if (!useSpecificRepairOrder && CanFitMoreNodes() && ShouldDelegateOrder(
"repairsystems") && OrderPrefab.Prefabs[
"repairsystems"] is OrderPrefab repairOrder && IsNonDuplicateOrderPrefab(repairOrder))
2561 AddOrderNodeWithIdentifier(
"repairsystems");
2566 if (CanFitMoreNodes() && IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs[
"extinguishfires"]) &&
2567 ActiveOrders.Any(o => o.Order.Identifier ==
"reportfire"))
2569 AddOrderNodeWithIdentifier(
"extinguishfires");
2571 if (CanFitMoreNodes() && characterContext?.Info?.Job?.Prefab?.AppropriateOrders !=
null)
2573 foreach (Identifier orderIdentifier
in characterContext.Info.Job.Prefab.AppropriateOrders)
2575 if (OrderPrefab.Prefabs[orderIdentifier] is OrderPrefab orderPrefab && IsNonDuplicateOrderPrefab(orderPrefab) &&
2576 shortcutNodes.None(n => n.UserData is Order order && order.Identifier == orderIdentifier) &&
2577 !orderPrefab.IsReport && orderPrefab.Category !=
null)
2579 if (!orderPrefab.MustSetTarget || orderPrefab.GetMatchingItems(sub,
true, interactableFor: characterContext ??
Character.Controlled).Any())
2581 var order = orderPrefab.CreateInstance(OrderPrefab.OrderTargetType.Entity);
2582 AddOrderNode(order);
2584 if (!CanFitMoreNodes()) {
break; }
2588 if (CanFitMoreNodes() && characterContext !=
null && !characterContext.IsDismissed)
2590 var order = OrderPrefab.Dismissal.CreateInstance(OrderPrefab.OrderTargetType.Entity);
2591 AddOrderNode(order);
2593 shortcutNodes.RemoveAll(n => n.UserData is Order o && !IsOrderAvailable(o));
2594 if (shortcutNodes.Count < 1) {
return; }
2595 shortcutCenterNode =
new GUIFrame(
new RectTransform(shortcutCenterNodeSize, parent: commandFrame.
RectTransform, anchor:
Anchor.Center), style:
null)
2597 CanBeFocused =
false
2599 CreateNodeIcon(shortcutCenterNode.
RectTransform,
"CommandShortcutNode");
2600 foreach (GUIComponent c
in shortcutCenterNode.
Children)
2602 c.HoverColor = c.Color;
2603 c.PressedColor = c.Color;
2604 c.SelectedColor = c.Color;
2607 int nodeCountForCalculations = shortcutNodes.Count * 2 + 2;
2608 Vector2[] offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, 0.75f * nodeDistance, nodeCountForCalculations);
2609 int firstOffsetIndex = nodeCountForCalculations / 2 - 1;
2610 for (
int i = 0; i < shortcutNodes.Count; i++)
2612 shortcutNodes[i].RectTransform.Parent = commandFrame.
RectTransform;
2613 shortcutNodes[i].
RectTransform.MoveOverTime(shorcutCenterNodeOffset + offsets[firstOffsetIndex - i].ToPoint(), CommandNodeAnimDuration);
2616 bool CanFitMoreNodes()
2618 return shortcutNodes.Count < maxShortcutNodeCount;
2620 static bool ShouldDelegateOrder(
string orderIdentifier) => ShouldDelegateOrderId(orderIdentifier.ToIdentifier());
2621 static bool ShouldDelegateOrderId(Identifier orderIdentifier)
2623 return Character.Controlled is not
Character c || !(c?.Info?.Job !=
null && c.Info.Job.Prefab.AppropriateOrders.Contains(orderIdentifier));
2625 bool IsNonDuplicateOrder(Order order) => IsNonDuplicateOrderPrefab(order.Prefab, order.Option);
2626 bool IsNonDuplicateOrderPrefab(OrderPrefab orderPrefab, Identifier option =
default)
2628 return characterContext ==
null || (option.IsEmpty ?
2629 characterContext.CurrentOrders.None(oi => oi?.Identifier == orderPrefab?.Identifier) :
2630 characterContext.CurrentOrders.None(oi => oi?.Identifier == orderPrefab?.Identifier && oi.Option == option));
2632 void AddOrderNodeWithIdentifier(
string identifier)
2634 var order = OrderPrefab.Prefabs[identifier].CreateInstance(OrderPrefab.OrderTargetType.Entity);
2635 AddOrderNode(order);
2637 void AddOrderNode(Order order)
2639 var node = order.Option.IsEmpty ?
2640 CreateOrderNode(shortcutNodeSize,
null, Point.Zero, order, -1) :
2641 CreateOrderOptionNode(shortcutNodeSize, null, Point.Zero, order, -1);
2642 shortcutNodes.Add(node);
2646 private void CreateOrderNodes(OrderCategory orderCategory)
2648 var orderPrefabs = OrderPrefab.Prefabs.Where(o => o.Category == orderCategory && !o.IsReport && IsOrderAvailable(o)).OrderBy(o => o.Identifier).ToArray();
2651 var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance,
2652 GetCircumferencePointCount(orderPrefabs.Length), GetFirstNodeAngle(orderPrefabs.Length));
2653 for (
int i = 0; i < orderPrefabs.Length; i++)
2655 order = orderPrefabs[i].CreateInstance(OrderPrefab.OrderTargetType.Entity);
2656 disableNode = !CanCharacterBeHeard() ||
2657 (order.MustSetTarget && (order.ItemComponentType !=
null || order.GetTargetItems().Any() || order.RequireItems.Any()) &&
2658 order.GetMatchingItems(
true, interactableFor: characterContext ??
Character.Controlled).None());
2659 optionNodes.Add(
new OptionNode(
2660 CreateOrderNode(nodeSize, commandFrame.
RectTransform, offsets[i].ToPoint(), order, (i + 1) % 10, disableNode: disableNode, checkIfOrderCanBeHeard:
false),
2661 !disableNode ? Keys.D0 + (i + 1) % 10 : Keys.None));
2671 private void CreateContextualOrderNodes()
2673 if (contextualOrders.None())
2676 if (itemContext !=
null && itemContext.IsPlayerTeamInteractable)
2679 foreach (OrderPrefab p
in OrderPrefab.Prefabs)
2681 targetComponent =
null;
2682 if (p.UseController && itemContext.Components.None(c => c is
Controller)) {
continue; }
2683 if (p.HasOptionSpecificTargetItems)
2685 foreach (Identifier option
in p.Options)
2687 if (p.TargetItemsMatchItem(itemContext, option))
2689 contextualOrders.Add(
new Order(p, option, itemContext, targetComponent));
2693 else if (p.TargetItemsMatchItem(itemContext) || p.TryGetTargetItemComponent(itemContext, out targetComponent))
2695 contextualOrders.Add(p.HasOptions ?
2696 p.CreateInstance(OrderPrefab.OrderTargetType.Entity) :
2697 new Order(p, itemContext, targetComponent));
2702 var operateWeaponsPrefab = OrderPrefab.Prefabs[
"operateweapons"];
2703 if (contextualOrders.None(o => o.Identifier ==
"operateweapons") && itemContext.Components.Any(c => c is
Controller))
2705 var turret = itemContext.GetConnectedComponents<
Turret>().FirstOrDefault(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item)) ??
2706 itemContext.GetConnectedComponents<
Turret>(recursive:
true).FirstOrDefault(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item));
2709 contextualOrders.Add(
new Order(operateWeaponsPrefab, turret.Item, turret));
2713 if (contextualOrders.None(order => order.Identifier ==
"repairsystems") && itemContext.Repairables.Any(r => r.IsBelowRepairThreshold))
2715 if (itemContext.Repairables.Any(r => r !=
null && r.RequiredSkills.Any(s => s !=
null && s.Identifier.Equals(
"electrical"))))
2717 contextualOrders.Add(
new Order(OrderPrefab.Prefabs[
"repairelectrical"], itemContext, targetItem:
null));
2719 else if (itemContext.Repairables.Any(r => r !=
null && r.RequiredSkills.Any(s => s !=
null && s.Identifier.Equals(
"mechanical"))))
2721 contextualOrders.Add(
new Order(OrderPrefab.Prefabs[
"repairmechanical"], itemContext, targetItem:
null));
2725 contextualOrders.Add(
new Order(OrderPrefab.Prefabs[
"repairsystems"], itemContext, targetItem:
null));
2729 if (contextualOrders.FirstOrDefault(order => order.Identifier.Equals(
"pumpwater")) is
Order pumpOrder &&
2730 itemContext.Components.FirstOrDefault(c => c.GetType() == pumpOrder.ItemComponentType) is
Pump pump && pump.
IsAutoControlled)
2732 contextualOrders.
Remove(pumpOrder);
2734 if (contextualOrders.None(info => info.Identifier.Equals(
"cleanupitems")))
2736 if (AIObjectiveCleanupItems.IsValidTarget(itemContext,
Character.Controlled, checkInventory:
false) || AIObjectiveCleanupItems.IsValidContainer(itemContext,
Character.Controlled))
2738 contextualOrders.Add(
new Order(OrderPrefab.Prefabs[
"cleanupitems"], itemContext, targetItem:
null));
2741 AddIgnoreOrder(itemContext);
2743 else if (hullContext !=
null)
2745 contextualOrders.Add(
new Order(OrderPrefab.Prefabs[
"fixleaks"], hullContext, targetItem:
null));
2746 if (wallContext !=
null)
2748 AddIgnoreOrder(wallContext);
2751 void AddIgnoreOrder(IIgnorable target)
2753 var orderIdentifier = Tags.IgnoreThis;
2754 if (!target.OrderedToBeIgnored && contextualOrders.None(order => order.Identifier == orderIdentifier))
2760 orderIdentifier = Tags.UnignoreThis;
2761 if (target.OrderedToBeIgnored && contextualOrders.None(order => order.Identifier == orderIdentifier))
2769 if (target is WallSection ws)
2771 contextualOrders.Add(
new Order(OrderPrefab.Prefabs[orderIdentifier], ws.Wall, ws.Wall.Sections.IndexOf(ws), orderGiver:
Character.Controlled));
2775 contextualOrders.Add(
new Order(OrderPrefab.Prefabs[orderIdentifier], target as Entity,
null,
Character.Controlled));
2779 if (contextualOrders.None(order => order.Identifier.Equals(
"wait")))
2781 Vector2 position = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
2782 Hull hull = Hull.FindHull(position, guess:
Character.Controlled?.CurrentHull);
2783 contextualOrders.Add(
new Order(OrderPrefab.Prefabs[
"wait"],
new OrderTarget(position, hull)));
2785 if (contextualOrders.None(order => order.Category !=
OrderCategory.Movement) && characters.Any(c => c !=
Character.Controlled))
2787 if (contextualOrders.None(order => order.Identifier.Equals(
"follow")))
2789 contextualOrders.Add(OrderPrefab.Prefabs[
"follow"].CreateInstance(OrderPrefab.OrderTargetType.Entity));
2793 if (contextualOrders.None(order => order.IsDismissal) && characters.Any(c => !c.IsDismissed))
2795 contextualOrders.Add(OrderPrefab.Dismissal.CreateInstance(OrderPrefab.OrderTargetType.Entity));
2798 contextualOrders.RemoveAll(o => !IsOrderAvailable(o));
2799 var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, contextualOrders.Count, MathHelper.ToRadians(90f + 180f / contextualOrders.Count));
2800 bool disableNode = !CanCharacterBeHeard();
2801 for (
int i = 0; i < contextualOrders.Count; i++)
2803 var order = contextualOrders[i];
2804 int hotkey = (i + 1) % 10;
2805 var component = order.Option.IsEmpty ?
2806 CreateOrderNode(nodeSize, commandFrame.
RectTransform, offsets[i].ToPoint(), order, hotkey, disableNode: disableNode, checkIfOrderCanBeHeard:
false) :
2807 CreateOrderOptionNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), order, hotkey);
2808 optionNodes.Add(
new OptionNode(component, !disableNode ? Keys.D0 + (i + 1) % 10 : Keys.None));
2813 private GUIButton CreateOrderNode(Point size, RectTransform parent, Point offset, Order order,
int hotkey,
bool disableNode =
false,
bool checkIfOrderCanBeHeard =
true)
2815 var node =
new GUIButton(
2816 new RectTransform(size, parent: parent, anchor:
Anchor.Center), style:
null)
2821 node.RectTransform.MoveOverTime(offset, CommandNodeAnimDuration);
2823 if (checkIfOrderCanBeHeard && !disableNode)
2825 disableNode = !CanCharacterBeHeard();
2828 bool mustSetOptionOrTarget = order.Prefab.HasOptions;
2829 Item orderTargetEntity =
null;
2834 if (!mustSetOptionOrTarget && order.MustSetTarget && itemContext ==
null)
2836 var matchingItems = order.GetMatchingItems(GetTargetSubmarine(),
true, interactableFor: characterContext ??
Character.Controlled);
2837 if (matchingItems.Count > 1)
2839 mustSetOptionOrTarget =
true;
2843 orderTargetEntity = matchingItems.FirstOrDefault();
2847 node.OnClicked = (button, userData) =>
2849 if (disableNode || !CanIssueOrders) {
return false; }
2850 var o = userData as
Order;
2851 if (mustSetOptionOrTarget)
2853 NavigateForward(button, userData);
2855 else if (o.MustManuallyAssign && characterContext ==
null)
2857 CreateAssignmentNodes(node);
2861 if (orderTargetEntity !=
null)
2863 o =
new Order(o.Prefab, orderTargetEntity, orderTargetEntity.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: order.OrderGiver);
2865 var character = !o.TargetAllCharacters ? characterContext ?? GetCharacterForQuickAssignment(o) : null;
2866 int priority = GetManualOrderPriority(character, o);
2867 SetCharacterOrder(character, o.WithManualPriority(priority).WithOrderGiver(
Character.Controlled));
2873 if (CanOpenManualAssignment(node))
2875 node.OnSecondaryClicked = (button, _) => CreateAssignmentNodes(button);
2877 var showAssignmentTooltip = !mustSetOptionOrTarget && characterContext ==
null && !order.MustManuallyAssign && !order.TargetAllCharacters;
2878 var orderName = GetOrderNameBasedOnContextuality(order);
2879 var icon = CreateNodeIcon(Vector2.One, node.RectTransform, order.SymbolSprite, order.Color,
2880 tooltip: !showAssignmentTooltip ? orderName : orderName +
2881 "\n" + PlayerInput.PrimaryMouseLabel +
": " + TextManager.Get(
"commandui.quickassigntooltip") +
2882 "\n" + PlayerInput.SecondaryMouseLabel +
": " + TextManager.Get(
"commandui.manualassigntooltip"));
2886 node.CanBeFocused = icon.CanBeFocused =
false;
2887 CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get(characterContext ==
null ?
"nocharactercanhear" :
"thischaractercanthear"));
2889 else if (hotkey >= 0)
2891 CreateHotkeyIcon(node.RectTransform, hotkey);
2901 private void CreateMinimapNodes(
Order order,
Submarine submarine, List<Item> matchingItems)
2907 if (subBorders.Width > subBorders.Height)
2911 frameSize.Y = (int)(frameSize.X * (subBorders.Height / (
float)subBorders.Width));
2917 frameSize.X = (int)(frameSize.Y * (subBorders.Width / (
float)subBorders.Height));
2921 targetFrame =
new GUIFrame(
2922 new RectTransform(frameSize, parent: commandFrame.
RectTransform, anchor: Anchor.Center)
2924 AbsoluteOffset = new Point(0, -150),
2925 Pivot = Pivot.BottomCenter
2927 style:
"InnerFrame");
2929 submarine.
CreateMiniMap(targetFrame, pointsOfInterest: matchingItems);
2931 new GUICustomComponent(
new RectTransform(Vector2.One, targetFrame.
RectTransform), onDraw: DrawMiniMapOverlay)
2933 CanBeFocused =
false,
2934 UserData = submarine
2937 List<GUIComponent> optionElements =
new List<GUIComponent>();
2938 foreach (Item item
in matchingItems)
2940 var itemTargetFrame = targetFrame.
Children.First().FindChild(item);
2941 if (itemTargetFrame ==
null) {
continue; }
2943 var anchor =
Anchor.TopLeft;
2944 if (itemTargetFrame.RectTransform.RelativeOffset.X < 0.5f)
2946 if (itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f)
2948 anchor =
Anchor.BottomRight;
2952 anchor =
Anchor.TopRight;
2955 else if (itemTargetFrame.RectTransform.RelativeOffset.Y < 0.5f)
2957 anchor =
Anchor.BottomLeft;
2960 var userData =
new MinimapNodeData
2964 var optionElement =
new GUIButton(
2966 new Point((
int)(50 * GUI.Scale)),
2967 parent: itemTargetFrame.RectTransform,
2971 UserData = userData,
2972 Font = GUIStyle.SmallFont,
2973 OnClicked = (button, obj) =>
2975 if (!CanIssueOrders) {
return false; }
2976 var o = (MinimapNodeData)obj;
2977 if (o.Order.Prefab.HasOptions)
2979 NavigateForward(button, o);
2981 else if (o.Order.MustManuallyAssign && characterContext ==
null)
2983 CreateAssignmentNodes(button);
2987 var character = characterContext ?? GetCharacterForQuickAssignment(o.Order);
2988 int priority = GetManualOrderPriority(character, o.Order);
2992 .WithManualPriority(priority)
2999 if (CanOpenManualAssignmentMinimapOrder(optionElement))
3001 optionElement.OnSecondaryClicked = (button, _) => CreateAssignmentNodes(button);
3003 var colorMultiplier = characters.Any(c => c.CurrentOrders.Any(o => o !=
null &&
3004 o.Identifier == userData.Order.Identifier &&
3005 o.TargetEntity == userData.Order.TargetEntity)) ? 0.5f : 1f;
3006 CreateNodeIcon(Vector2.One, optionElement.RectTransform, item.Prefab.MinimapIcon ?? order.
SymbolSprite, order.
Color * colorMultiplier, tooltip: item.Name);
3007 optionNodes.Add(
new OptionNode(optionElement, Keys.None));
3008 optionElements.Add(optionElement);
3011 Rectangle clampArea =
new Rectangle(10, 10, GameMain.GraphicsWidth - 20, GameMain.GraphicsHeight - 20);
3013 Point originalSize = disallowedArea.Size;
3014 disallowedArea.Size = disallowedArea.MultiplySize(0.9f);
3015 disallowedArea.X += (originalSize.X - disallowedArea.Size.X) / 2;
3016 disallowedArea.Y += (originalSize.Y - disallowedArea.Size.Y) / 2;
3017 GUI.PreventElementOverlap(optionElements,
new List<Rectangle>() { disallowedArea }, clampArea);
3021 var shadow =
new GUIFrame(
3022 new RectTransform(targetFrame.
Rect.Size +
new Point((
int)(200 * GUI.Scale)), targetFrame.
RectTransform, anchor:
Anchor.Center),
3024 color: matchingItems.Count > 1 ? Color.Black * 0.9f : Color.Black * 0.7f);
3025 shadow.SetAsFirstChild();
3028 private void CreateOrderOptionNodes(Order order, Item targetItem)
3030 if (itemContext !=
null)
3032 targetItem = !order.UseController ? itemContext :
3033 itemContext.GetConnectedComponents<
Turret>().FirstOrDefault()?.
Item ?? itemContext.GetConnectedComponents<
Turret>(recursive:
true).FirstOrDefault()?.
Item;
3035 var o = targetItem ==
null ? order : order.WithItemComponent(targetItem, order.GetTargetItemComponent(targetItem));
3036 var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance,
3037 GetCircumferencePointCount(order.Options.Length),
3038 GetFirstNodeAngle(order.Options.Length));
3039 var offsetIndex = 0;
3040 for (
int i = 0; i < order.Options.Length; i++)
3042 optionNodes.Add(
new OptionNode(
3043 CreateOrderOptionNode(nodeSize, commandFrame.
RectTransform, offsets[offsetIndex++].ToPoint(), o.WithOption(order.Options[i]), (i + 1) % 10),
3044 Keys.D0 + (i + 1) % 10));
3048 private GUIButton CreateOrderOptionNode(Point size, RectTransform parent, Point offset, Order order,
int hotkey)
3050 var node =
new GUIButton(
new RectTransform(size, parent: parent, anchor:
Anchor.Center), style:
null)
3053 OnClicked = (button, userData) =>
3055 if (!CanIssueOrders) {
return false; }
3056 var o = userData as
Order;
3057 if (o.MustManuallyAssign && characterContext ==
null)
3059 CreateAssignmentNodes(button);
3063 var character = characterContext ?? GetCharacterForQuickAssignment(o);
3064 int priority = GetManualOrderPriority(character, o);
3065 SetCharacterOrder(character, o.WithManualPriority(priority).WithOrderGiver(
Character.Controlled));
3071 if (CanOpenManualAssignment(node))
3073 node.OnSecondaryClicked = (button, _) => CreateAssignmentNodes(button);
3075 node.RectTransform.MoveOverTime(offset, CommandNodeAnimDuration);
3077 GUIImage icon =
null;
3078 if (order.Prefab.OptionSprites.TryGetValue(order.Option, out Sprite sprite))
3080 var optionName = order.Prefab.GetOptionName(order.Option);
3081 var showAssignmentTooltip = characterContext ==
null && !order.MustManuallyAssign && !order.TargetAllCharacters;
3082 icon = CreateNodeIcon(Vector2.One, node.RectTransform, sprite, order.Color,
3083 tooltip: characterContext !=
null ? optionName : optionName +
3084 "\n" + PlayerInput.PrimaryMouseLabel +
": " + TextManager.Get(
"commandui.quickassigntooltip") +
3085 "\n" + PlayerInput.SecondaryMouseLabel +
": " + TextManager.Get(
"commandui.manualassigntooltip"));
3087 if (!CanCharacterBeHeard())
3089 node.CanBeFocused =
false;
3090 if (icon !=
null) { icon.CanBeFocused =
false; }
3091 CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get(characterContext ==
null ?
"nocharactercanhear" :
"thischaractercanthear"));
3093 else if (hotkey >= 0)
3095 CreateHotkeyIcon(node.RectTransform, hotkey);
3100 private bool CreateAssignmentNodes(GUIComponent node)
3102 if (centerNode ==
null)
3108 var order = node.UserData is MinimapNodeData minimapNodeData ? minimapNodeData.Order : node.UserData as
Order;
3109 var characters = GetCharactersForManualAssignment(order);
3110 if (characters.None()) {
return false; }
3112 if (!(optionNodes.Find(n => n.Button == node) is OptionNode optionNode) || !optionNodes.Remove(optionNode))
3114 shortcutNodes.Remove(node);
3116 RemoveOptionNodes();
3118 if (returnNode !=
null)
3120 returnNode.
Children.ForEach(child => child.Visible =
false);
3122 historyNodes.Push(returnNode);
3124 SetReturnNode(centerNode,
new Point(0, (
int)(returnNodeDistanceModifier * nodeDistance)));
3126 if (targetFrame ==
null || !targetFrame.
Visible)
3128 SetCenterNode(node as GUIButton);
3132 if (order.Option.IsEmpty)
3134 SetCenterNode(node as GUIButton, resetAnchor:
true);
3138 var clickedOptionNode =
new GUIButton(
3139 new RectTransform(centerNodeSize, parent: commandFrame.
RectTransform, anchor:
Anchor.Center),
3142 UserData = node.UserData
3144 if (order.Prefab.OptionSprites.TryGetValue(order.Option, out Sprite sprite))
3146 CreateNodeIcon(Vector2.One, clickedOptionNode.RectTransform, sprite, order.Color, tooltip: order.GetOptionName(order.Option));
3148 SetCenterNode(clickedOptionNode);
3153 if (shortcutCenterNode !=
null)
3156 shortcutCenterNode =
null;
3159 var characterCount = characters.Count;
3162 var needToExpand = characterCount > 10;
3163 if (characterCount > 5)
3166 var charactersOnFirstRing = needToExpand ? 5 : (int)Math.Floor(characterCount / 2f);
3167 offsets = GetAssignmentNodeOffsets(charactersOnFirstRing);
3168 for (
int i = 0; i < charactersOnFirstRing; i++)
3170 CreateAssignmentNode(order, characters[i], offsets[i].ToPoint(), hotkey++ % 10);
3173 var charactersOnSecondRing = needToExpand ? 4 : characterCount - charactersOnFirstRing;
3174 offsets = GetAssignmentNodeOffsets(needToExpand ? 5 : charactersOnSecondRing,
false);
3175 for (
int i = 0; i < charactersOnSecondRing; i++)
3177 CreateAssignmentNode(order, characters[charactersOnFirstRing + i], offsets[i].ToPoint(), hotkey++ % 10);
3182 offsets = GetAssignmentNodeOffsets(characterCount);
3183 for (
int i = 0; i < characterCount; i++)
3185 CreateAssignmentNode(order, characters[i], offsets[i].ToPoint(), hotkey++ % 10);
3191 hotkey = optionNodes.Count + 1;
3192 CreateHotkeyIcon(returnNode.
RectTransform, hotkey % 10,
true);
3193 returnNodeHotkey = Keys.D0 + hotkey % 10;
3194 expandNodeHotkey = Keys.None;
3198 extraOptionCharacters.Clear();
3200 extraOptionCharacters.AddRange(characters.GetRange(hotkey - 1, characterCount - (hotkey - 1))
3201 .OrderBy(c => c?.Info?.Job?.Name).ThenBy(c => c?.Info?.DisplayName));
3203 expandNode =
new GUIButton(
3204 new RectTransform(assignmentNodeSize, parent: commandFrame.
RectTransform, anchor:
Anchor.Center)
3206 AbsoluteOffset = offsets.Last().ToPoint()
3211 OnClicked = ExpandAssignmentNodes
3213 CreateNodeIcon(expandNode.
RectTransform,
"CommandExpandNode", order.Color, tooltip: TextManager.Get(
"commandui.expand"));
3215 hotkey = optionNodes.Count + 1;
3217 expandNodeHotkey = Keys.D0 + hotkey % 10;
3218 CreateHotkeyIcon(returnNode.
RectTransform, ++hotkey % 10,
true);
3219 returnNodeHotkey = Keys.D0 + hotkey % 10;
3223 private Vector2[] GetAssignmentNodeOffsets(
int characters,
bool firstRing =
true)
3225 var nodeDistance = 1.8f * this.nodeDistance;
3226 var nodePositionsOnEachSide = characters % 2 > 0 ? 7 : 6;
3227 var nodeCountForCalculation = 2 * nodePositionsOnEachSide + 2;
3228 var offsets = MathUtils.GetPointsOnCircumference(firstRing ?
new Vector2(0f, 0.5f * nodeDistance) : Vector2.Zero,
3229 nodeDistance, nodeCountForCalculation, MathHelper.ToRadians(180f + 360f / nodeCountForCalculation));
3230 var emptySpacesPerSide = (nodePositionsOnEachSide - characters) / 2;
3231 var offsetsInUse =
new Vector2[nodePositionsOnEachSide - 2 * emptySpacesPerSide];
3232 for (
int i = 0; i < offsetsInUse.Length; i++)
3234 offsetsInUse[i] = offsets[i + emptySpacesPerSide];
3236 return offsetsInUse;
3239 private bool ExpandAssignmentNodes(GUIButton node,
object userData)
3241 node.OnClicked = (button, _) =>
3243 RemoveExtraOptionNodes();
3244 button.OnClicked = ExpandAssignmentNodes;
3248 var availableNodePositions = 20;
3249 var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, 2.7f *
this.nodeDistance, availableNodePositions,
3250 firstAngle: MathHelper.ToRadians(-90f - ((extraOptionCharacters.Count - 1) * 0.5f * (360f / availableNodePositions))));
3251 for (
int i = 0; i < extraOptionCharacters.Count && i < availableNodePositions; i++)
3253 CreateAssignmentNode(userData as Order, extraOptionCharacters[i], offsets[i].ToPoint(), -1, nameLabelScale: 1.15f);
3258 private void CreateAssignmentNode(Order order, Character character, Point offset,
int hotkey,
float nameLabelScale = 1f)
3261 var node =
new GUIButton(
3262 new RectTransform(assignmentNodeSize, parent: commandFrame.
RectTransform, anchor:
Anchor.Center),
3265 UserData = character,
3266 OnClicked = (_, userData) =>
3268 if (!CanIssueOrders) {
return false; }
3270 int priority = GetManualOrderPriority(character, order);
3271 SetCharacterOrder(character, order.WithManualPriority(priority).WithOrderGiver(
Character.Controlled));
3276 node.RectTransform.MoveOverTime(offset, CommandNodeAnimDuration);
3278 var jobColor = character.Info?.Job?.Prefab?.UIColor ?? Color.White;
3281 var topOrderInfo = character.GetCurrentOrderWithTopPriority();
3283 if (topOrderInfo !=
null)
3285 orderIcon =
new GUIImage(
new RectTransform(
new Vector2(1.2f), node.RectTransform, anchor:
Anchor.Center), topOrderInfo.SymbolSprite, scaleToFit:
true);
3286 var tooltip = topOrderInfo.Name;
3287 if (topOrderInfo.Option != Identifier.Empty) { tooltip +=
" (" + topOrderInfo.GetOptionName(topOrderInfo.Option) +
")"; };
3288 orderIcon.ToolTip = tooltip;
3292 orderIcon =
new GUIImage(
new RectTransform(
new Vector2(1.2f), node.RectTransform, anchor:
Anchor.Center),
"CommandIdleNode", scaleToFit:
true);
3294 orderIcon.Color = jobColor * nodeColorMultiplier;
3295 orderIcon.HoverColor = jobColor;
3296 orderIcon.PressedColor = jobColor;
3297 orderIcon.SelectedColor = jobColor;
3298 orderIcon.UserData =
"colorsource";
3301 var width = (int)(nameLabelScale * nodeSize.X);
3302 var font = GUIStyle.SmallFont;
3303 var nameLabel =
new GUITextBlock(
3304 new RectTransform(
new Point(width, 0), parent: node.RectTransform, anchor:
Anchor.TopCenter, pivot:
Pivot.BottomCenter)
3306 RelativeOffset = new Vector2(0f, -0.25f)
3308 ToolBox.LimitString(character.Info?.DisplayName, font, width), textColor: jobColor * nodeColorMultiplier, font: font, textAlignment: Alignment.Center, style:
null)
3310 CanBeFocused =
false,
3312 HoverTextColor = jobColor
3315 if (character.Info?.Job?.Prefab?.IconSmall is Sprite smallJobIcon)
3319 new RectTransform(
new Vector2(0.4f), node.RectTransform, anchor:
Anchor.TopCenter, pivot:
Pivot.Center)
3321 RelativeOffset = new Vector2(0.0f, -((orderIcon.RectTransform.RelativeSize.Y - 1) / 2))
3323 smallJobIcon, scaleToFit:
true)
3325 CanBeFocused =
false,
3327 HoverColor = jobColor
3331 bool canHear = character.CanHearCharacter(
Character.Controlled);
3333 if (
Character.Controlled ==
null) { canHear =
true; }
3338 node.CanBeFocused = orderIcon.CanBeFocused =
false;
3339 CreateBlockIcon(node.RectTransform, tooltip: TextManager.Get(
"thischaractercanthear"));
3343 if (canHear) { CreateHotkeyIcon(node.RectTransform, hotkey); }
3344 optionNodes.Add(
new OptionNode(node, canHear ? Keys.D0 + hotkey : Keys.None));
3348 extraOptionNodes.Add(node);
3352 private GUIImage CreateNodeIcon(Vector2 relativeSize, RectTransform parent, Sprite sprite, Color color, LocalizedString tooltip =
null)
3355 return new GUIImage(
3356 new RectTransform(relativeSize, parent),
3360 Color = color * nodeColorMultiplier,
3362 PressedColor = color,
3363 SelectedColor = color,
3365 UserData =
"colorsource"
3372 private GUIImage CreateNodeIcon(Point absoluteSize, RectTransform parent, Sprite sprite, Color color, LocalizedString tooltip =
null)
3375 return new GUIImage(
3376 new RectTransform(absoluteSize, parent: parent) { IsFixedSize =
true },
3380 Color = color * nodeColorMultiplier,
3382 PressedColor = color,
3383 SelectedColor = color,
3385 UserData =
"colorsource"
3389 private void CreateNodeIcon(RectTransform parent,
string style, Color? color =
null, LocalizedString tooltip =
null)
3392 var icon =
new GUIImage(
3393 new RectTransform(Vector2.One, parent),
3398 UserData =
"colorsource"
3402 icon.Color = color.Value * nodeColorMultiplier;
3403 icon.HoverColor = color.Value;
3407 icon.Color = icon.HoverColor * nodeColorMultiplier;
3411 private void CreateHotkeyIcon(RectTransform parent,
int hotkey,
bool enlargeIcon =
false)
3413 var bg =
new GUIImage(
3414 new RectTransform(
new Vector2(enlargeIcon ? 0.4f : 0.25f), parent, anchor:
Anchor.BottomCenter, pivot:
Pivot.Center),
3415 "CommandHotkeyContainer",
3418 CanBeFocused =
false,
3422 new RectTransform(Vector2.One, bg.RectTransform, anchor:
Anchor.Center),
3424 textColor: Color.Black,
3425 textAlignment: Alignment.Center)
3427 CanBeFocused =
false
3431 private void CreateBlockIcon(RectTransform parent, LocalizedString tooltip =
null)
3433 var icon =
new GUIImage(
new RectTransform(
new Vector2(0.9f), parent, anchor:
Anchor.Center), cancelIcon, scaleToFit:
true)
3435 CanBeFocused =
false,
3436 Color = GUIStyle.Red * nodeColorMultiplier,
3437 HoverColor = GUIStyle.Red
3439 if (!tooltip.IsNullOrEmpty())
3441 string color = XMLExtensions.ColorToString(GUIStyle.Red);
3442 tooltip = $
"‖color:{color}‖{tooltip}‖color:end‖";
3443 icon.ToolTip = RichString.Rich(tooltip);
3444 icon.CanBeFocused =
true;
3448 private int GetCircumferencePointCount(
int nodes)
3450 return nodes % 2 > 0 ? nodes : nodes + 1;
3453 private float GetFirstNodeAngle(
int nodeCount)
3455 var bearing = 90.0f;
3456 if (returnNode !=
null)
3458 bearing = GetBearing(
3462 else if (shortcutCenterNode !=
null)
3464 bearing = GetBearing(
3466 shorcutCenterNodeOffset.ToVector2());
3468 return nodeCount % 2 > 0 ?
3469 MathHelper.ToRadians(bearing + 360.0f / nodeCount / 2) :
3470 MathHelper.ToRadians(bearing + 360.0f / (nodeCount + 1));
3473 private float GetBearing(Vector2 startPoint, Vector2 endPoint,
bool flipY =
false,
bool flipX =
false)
3475 var radians = Math.Atan2(
3476 !flipY ? endPoint.Y - startPoint.Y : startPoint.Y - endPoint.Y,
3477 !flipX ? endPoint.X - startPoint.X : startPoint.X - endPoint.X);
3478 var degrees = MathHelper.ToDegrees((
float)radians);
3479 return (degrees < 0) ? (degrees + 360) : degrees;
3482 private bool TryGetBreachedHullAtHoveredWall(out Hull breachedHull, out WallSection hoveredWall)
3484 breachedHull =
null;
3487 List<Gap> leaks = Gap.GapList.FindAll(g =>
3488 g !=
null && g.ConnectedWall !=
null && g.ConnectedDoor ==
null && g.Open > 0 && g.linkedTo.Any(l => l !=
null) &&
3489 g.Submarine !=
null && (
Character.Controlled !=
null && g.Submarine.TeamID ==
Character.Controlled.TeamID && g.Submarine.Info.IsPlayer));
3490 if (leaks.None()) {
return false; }
3491 Vector2 mouseWorldPosition = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
3492 foreach (Gap leak
in leaks)
3494 if (
Submarine.RectContains(leak.ConnectedWall.WorldRect, mouseWorldPosition))
3496 breachedHull = leak.FlowTargetHull;
3497 foreach (var section
in leak.ConnectedWall.Sections)
3499 if (
Submarine.RectContains(section.WorldRect, mouseWorldPosition))
3501 hoveredWall = section;
3523 if (
Character.Controlled.Submarine is Submarine currentSub && currentSub != sub && currentSub.TeamID ==
Character.Controlled.TeamID && !currentSub.IsConnectedTo(sub))
3531 private void SetCharacterTooltip(GUIComponent component, Character character)
3533 if (component ==
null) {
return; }
3534 LocalizedString tooltip = character?.Info !=
null ? characterContext.Info.DisplayName :
null;
3535 if (tooltip.IsNullOrWhiteSpace()) { component.ToolTip = tooltip;
return; }
3536 if (character.Info?.Job !=
null && !characterContext.Info.Job.Name.IsNullOrWhiteSpace()) { tooltip +=
" (" + characterContext.Info.Job.Name +
")"; }
3537 component.ToolTip = tooltip;
3540 private LocalizedString GetOrderNameBasedOnContextuality(Order order)
3542 if (order ==
null) {
return ""; }
3543 if (isContextual) {
return order.ContextualName; }
3547 private int GetManualOrderPriority(Character character, Order order)
3549 return character?.Info?.GetManualOrderPriority(order) ?? CharacterInfo.HighestManualOrderPriority;
3552 private bool IsOrderAvailable(Order order)
3553 => IsOrderAvailable(order.Prefab);
3555 private bool IsOrderAvailable(OrderPrefab order)
3557 if (order ==
null) {
return false; }
3558 switch (order.Identifier.Value.ToLowerInvariant())
3560 case "assaultenemy":
3562 if (character?.Submarine ==
null) {
return false; }
3563 return character.Submarine.GetConnectedSubs().Any(s => s.TeamID != character.TeamID);
3569 #region Crew Member Assignment Logic
3570 private bool CanOpenManualAssignmentMinimapOrder(GUIComponent node)
3572 if (node ==
null || characterContext !=
null) {
return false; }
3573 if (node.UserData is MinimapNodeData {Order: { } minimapOrder})
3575 return !minimapOrder.TargetAllCharacters && (!minimapOrder.Prefab.HasOptions || !minimapOrder.Option.IsEmpty);
3577 if (node.UserData is Order nodeOrder)
3579 return !nodeOrder.TargetAllCharacters && !nodeOrder.Prefab.HasOptions &&
3580 (!nodeOrder.MustSetTarget || itemContext !=
null ||
3581 nodeOrder.GetMatchingItems(GetTargetSubmarine(),
true, interactableFor:
Character.Controlled).Count < 2);
3586 private bool CanOpenManualAssignment(GUIComponent node)
3588 if (node ==
null || characterContext !=
null) {
return false; }
3589 if (node.UserData is Order nodeOrder)
3591 return !nodeOrder.TargetAllCharacters &&
3592 (!nodeOrder.Prefab.HasOptions || !nodeOrder.Option.IsEmpty) &&
3593 (!nodeOrder.MustSetTarget || itemContext !=
null || nodeOrder.GetMatchingItems(GetTargetSubmarine(),
true, interactableFor:
Character.Controlled).Count < 2);
3598 private Character GetCharacterForQuickAssignment(Order order)
3600 return GetCharacterForQuickAssignment(order,
Character.Controlled, characters);
3603 private List<Character> GetCharactersForManualAssignment(Order order)
3606 if (
Character.Controlled ==
null) {
return new List<Character>(); }
3608 if (order.Identifier == dismissedOrderPrefab.Identifier)
3610 return characters.Union(GetOrderableFriendlyNPCs()).Where(c => !c.IsDismissed).OrderBy(c => c.Info.DisplayName).ToList();
3612 return GetCharactersSortedForOrder(order, characters,
Character.Controlled, order.Identifier !=
"follow", extraCharacters: GetOrderableFriendlyNPCs()).ToList();
3615 private IEnumerable<Character> GetOrderableFriendlyNPCs()
3618 return crewList.Content.Children.Where(c => c.UserData is Character character && character.TeamID ==
CharacterTeamType.FriendlyNPC).Select(c => (Character)c.UserData);
3632 bool canIssueOrders =
false;
3644 if (!ReportButtonFrame.Visible) {
return; }
3647 if (reportButtonParent ==
null) {
return; }
3649 ReportButtonFrame.RectTransform.AbsoluteOffset =
new Point(reportButtonParent.GUIFrame.Rect.Right + (
int)(10 * GUI.Scale), reportButtonParent.GUIFrame.Rect.Y);
3652 ToggleReportButton(
"reportfire", hasFires);
3655 ToggleReportButton(
"reportbreach", hasLeaks);
3658 ToggleReportButton(
"reportintruders", hasIntruders);
3663 if (highlight.Visible)
3671 ReportButtonFrame.Visible =
false;
3675 private void ToggleReportButton(
string orderIdentifier,
bool enabled)
3677 ToggleReportButton(orderIdentifier.ToIdentifier(), enabled);
3680 private void ToggleReportButton(Identifier orderIdentifier,
bool enabled)
3682 var reportButton = ReportButtonFrame.FindChild(c => c.UserData is OrderPrefab orderPrefab && orderPrefab.Identifier == orderIdentifier);
3683 if (reportButton !=
null)
3685 reportButton.GetChildByUserData(
"highlighted").Visible = enabled;
3693 crewList.ClearChildren();
3700 characterInfos.Clear();
3701 crewList.ClearChildren();
3707 public XElement
Save(XElement parentElement)
3709 var element =
new XElement(
"crew");
3710 for (
int i = 0; i < characterInfos.Count; i++)
3712 var ci = characterInfos[i];
3713 var infoElement = ci.Save(element);
3714 if (ci.InventoryData !=
null) { infoElement.Add(ci.InventoryData); }
3715 if (ci.HealthData !=
null) { infoElement.Add(ci.HealthData); }
3716 if (ci.OrderData !=
null) { infoElement.Add(ci.OrderData); }
3717 infoElement.Add(
new XAttribute(
"crewlistindex", ci.CrewListIndex));
3718 if (ci.LastControlled) { infoElement.Add(
new XAttribute(
"lastcontrolled",
true)); }
3720 parentElement?.Add(element);
3727 if (count < 1) {
return; }
3728 var activeOrders =
new List<(Order, float?)>();
3729 for (ushort i = 0; i < count; i++)
3738 if (orderMessageInfo.OrderIdentifier == Identifier.Empty)
3740 DebugConsole.ThrowError(
"Invalid active order - order identifier empty.");
3747 new Order(orderPrefab, orderMessageInfo.TargetEntity, orderPrefab.
GetTargetItemComponent(orderMessageInfo.TargetEntity as
Item), orderGiver: orderGiver),
3749 new Order(orderPrefab, orderMessageInfo.TargetPosition, orderGiver: orderGiver),
3751 new Order(orderPrefab, orderMessageInfo.TargetEntity as
Structure, orderMessageInfo.WallSectionIndex, orderGiver: orderGiver),
3752 _ =>
throw new NotImplementedException()
3757 activeOrders.Add((order, fadeOutTime));
3760 foreach (var (order, fadeOutTime) in activeOrders)
3762 if (order.IsIgnoreOrder)
3764 switch (order.TargetType)
3767 if (order.TargetEntity is not
IIgnorable ignorableEntity) {
break; }
3768 ignorableEntity.OrderedToBeIgnored = order.Identifier == Tags.IgnoreThis;
3771 throw new NotImplementedException();
3773 if (!order.WallSectionIndex.HasValue) {
break; }
3774 if (order.TargetEntity is not
Structure s) {
break; }
3775 if (s.GetSection(order.WallSectionIndex.Value) is not
IIgnorable ignorableWall) {
break; }
3776 ignorableWall.OrderedToBeIgnored = order.Identifier == Tags.IgnoreThis;
override bool IsValidTarget(Character target)
static Sprite GetSprite(Identifier identifier, Identifier option, Entity targetEntity)
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
static readonly List< Character > CharacterList
static Character? Controlled
void SetOrder(Order order, bool isNewOrder, bool speak=true, bool force=false)
Force an order to be set for the character, bypassing hearing checks
Stores information about the Character that is needed between rounds in the menu etc....
static int HighestManualOrderPriority
const int MaxCurrentOrders
bool CloseAfterMessageSent
readonly ChatManager ChatManager
ChatBox(GUIComponent parent, bool isSinglePlayer)
void AddMessage(ChatMessage message)
bool TypingChatMessage(GUITextBox textBox, string text)
void Store(string message)
Triggers a "conversation popup" with text and support for different branching options.
Responsible for keeping track of the characters in the player crew, saving and loading their orders,...
static void ClientReadActiveOrders(IReadMessage inc)
void AddSinglePlayerChatMessage(ChatMessage message)
ChatBox ChatBox
Present only in single player games. In multiplayer. The chatbox is found from GameSession....
bool CharacterClicked(GUIComponent component, object selection)
Sets which character is selected in the crew UI (highlight effect etc)
void CreateObjectiveIcon(Character character, Identifier identifier, Identifier option, Entity targetEntity)
void SetCharacterSpeaking(Character character)
void SetClientSpeaking(Client client)
void SelectPreviousCharacter()
GUIComponent ReportButtonFrame
void SetOrderHighlight(Character character, Identifier orderIdentifier, Identifier orderOption)
void OpenCommandUI(Entity entityContext=null, bool forceContextual=false)
Rectangle GetActiveCrewArea()
void SetCharacterOrder(Character character, Order order, bool isNewOrder=true)
Sets the character's current order (if it's close enough to receive messages from orderGiver) and dis...
void KillCharacter(Character killedCharacter, bool resetCrewListIndex=true)
static bool PreferCrewMenuOpen
void ReviveCharacter(Character revivedCharacter)
bool AllowCharacterSwitch
void RemoveCharacterFromCrewList(Character character)
bool AddOrder(Order order, float? fadeOutTime)
void AddSinglePlayerChatMessage(string senderName, string text, ChatMessageType messageType, Entity sender)
void AddCurrentOrderIcon(Character character, Order order)
Displays the specified order in the crew UI next to the character.
CrewManager(XElement element, bool isSinglePlayer)
GUIComponent AddCharacterToCrewList(Character character)
Add character to the list without actually adding it to the crew
void SetPlayerVoiceIconState(Client client, bool muted, bool mutedLocally)
XElement Save(XElement parentElement)
Saves the current crew. Note that this is client-only code (only used in the single player campaign) ...
void UpdateReports()
Enables/disables report buttons when needed
void AddToGUIUpdateList()
List< GUIButton > OrderOptionButtons
void InitSinglePlayerRound()
bool IsCrewMenuOpen
This property stores the preference in settings. Don't use for automatic logic. Use AutoShowCrewList(...
void AddSinglePlayerChatMessage(LocalizedString senderName, LocalizedString text, ChatMessageType messageType, Entity sender)
Adds the message to the single player chatbox.
void AddCharacterElements(XElement element)
OrderPrefab DraggedOrderPrefab
static void CreateReportButtons(CrewManager crewManager, GUIComponent parent, IReadOnlyList< OrderPrefab > reports, bool isHorizontal)
static bool? CanIssueOrders
void SelectNextCharacter()
const ushort NullEntityID
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
GUIComponent GetChild(int index)
virtual void RemoveChild(GUIComponent child)
virtual void AddToGUIUpdateList(bool ignoreChildren=false, int order=0)
GUIComponent GetChildByUserData(object obj)
GUIComponent FindChild(Func< GUIComponent, bool > predicate, bool recursive=false)
virtual RichString ToolTip
IEnumerable< GUIComponent > GetAllChildren()
Returns all child elements in the hierarchy.
bool IsChildOf(GUIComponent component, bool recursive=true)
RectTransform RectTransform
IEnumerable< GUIComponent > Children
override void RemoveChild(GUIComponent child)
GUIFrame Content
A frame that contains the contents of the listbox. The frame itself is not rendered.
void UpdateScrollBarSize()
OnTextChangedHandler OnTextChanged
Don't set the Text property on delegates that register to this event, because modifying the Text will...
static GameSession?? GameSession
static int GraphicsHeight
static bool IsMultiplayer
static NetworkMember NetworkMember
LocalizedString DisplayName
readonly List< Gap > ConnectedGaps
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
List< FireSource > FireSources
static void ReportProblem(Character reporter, Order order, Hull targetHull=null)
static HashSet< Item > DeconstructItems
Items that have been marked for deconstruction
The base class for components holding the different functionalities of the item
readonly List< Skill > RequiredSkills
static void CreateModerationContextMenu(Client client)
static string GetChatMessageCommand(string message, out string messageWithoutCommand)
static Color[] MessageColor
static ChatMessage Create(string senderName, string text, ChatMessageType type, Entity sender, Client client=null, PlayerConnectionChangeType changeType=PlayerConnectionChangeType.None, Color? textColor=null)
Character SenderCharacter
static bool CanUseRadio(Character sender, bool ignoreJamming=false)
void Vote(VoteType voteType, object data)
override IReadOnlyList< Client > ConnectedClients
void SendChatMessage(ChatMessage msg)
static OrderMessageInfo ReadOrder(IReadMessage msg)
static void UpdateVoiceIndicator(GUIImage soundIcon, float voipAmplitude, float deltaTime)
readonly Entity TargetEntity
ItemComponent GetTargetItemComponent(Item item)
Get the target item component based on the target item type
string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, Identifier orderOption=default, bool isNewOrder=true)
readonly Character OrderGiver
Order WithType(OrderType type)
readonly? int WallSectionIndex
readonly Identifier Option
Order WithTargetEntity(Entity entity)
Order WithItemComponent(Item item, ItemComponent component=null)
Order WithWallSection(Structure wall, int? sectionIndex)
bool MatchesOrder(Identifier orderIdentifier, Identifier orderOption)
readonly OrderTargetType TargetType
OrderPrefab(ContentXElement orderElement, OrdersFile file)
readonly Sprite SymbolSprite
readonly float FadeOutTime
static readonly PrefabCollection< OrderPrefab > Prefabs
Order CreateInstance(OrderTargetType targetType, Character orderGiver=null, bool isAutonomous=false)
Create an Order instance with a null target
ItemComponent GetTargetItemComponent(Item item)
Get the target item component based on the target item type
bool IsVisibleAsReportButton
static RichString Rich(LocalizedString str, Func< string, string >? postProcess=null)
void CreateMiniMap(GUIComponent parent, IEnumerable< Entity > pointsOfInterest=null, bool ignoreOutpost=false)
Rectangle GetDockedBorders(bool allowDifferentTeam=true)
Returns a rect that contains the borders of this sub and all subs docked to it, excluding outposts
@ SelectPreviousCharacter