3 using Microsoft.Xna.Framework;
4 using Microsoft.Xna.Framework.Graphics;
6 using System.Collections.Generic;
7 using System.Collections.Immutable;
9 using Microsoft.Xna.Framework.Input;
13 internal readonly
struct MiniMapGUIComponent
15 public readonly GUIComponent RectComponent;
16 public readonly GUIComponent BorderComponent;
18 public MiniMapGUIComponent(GUIComponent rectComponent)
20 RectComponent = rectComponent;
21 BorderComponent = rectComponent;
24 public MiniMapGUIComponent(GUIComponent frame, GUIComponent linkedHullComponent)
26 RectComponent = frame;
27 BorderComponent = linkedHullComponent;
30 public void Deconstruct(out GUIComponent component, out GUIComponent borderComponent)
32 component = RectComponent;
33 borderComponent = BorderComponent;
37 internal readonly
struct MiniMapSprite
39 public readonly Sprite? Sprite;
40 public readonly Color Color;
42 public MiniMapSprite(JobPrefab prefab)
44 Sprite = prefab.IconSmall;
45 Color = prefab.UIColor;
48 public MiniMapSprite(Order order)
50 Sprite = order.SymbolSprite;
55 internal readonly
struct MiniMapHullData
57 public readonly List<List<Vector2>> Polygon;
58 public readonly (RectangleF Rect, Hull Hull)[] RectDatas;
59 public readonly RectangleF Bounds;
60 public readonly Point ParentSize;
62 public MiniMapHullData(List<List<Vector2>> polygon, RectangleF bounds, Point parentSize, ImmutableArray<RectangleF> rects, ImmutableArray<Hull> hulls)
64 ParentSize = parentSize;
67 int count = Math.Min(rects.Length, hulls.Length);
68 RectDatas =
new (RectangleF Rect, Hull Hull)[count];
69 for (
int i = 0; i < count; i++)
71 RectDatas[i] = (rects[i], hulls[i]);
76 internal enum MiniMapMode
84 internal readonly
struct RelativeEntityRect
86 public readonly Vector2 RelativePosition;
87 public readonly Vector2 RelativeSize;
89 public RelativeEntityRect(RectangleF worldBorders, RectangleF entityRect)
91 RelativePosition =
new Vector2((entityRect.X - worldBorders.X) / worldBorders.Width, (worldBorders.Y - entityRect.Y) / worldBorders.Height);
92 RelativeSize =
new Vector2(entityRect.Width / worldBorders.Width, entityRect.Height / worldBorders.Height);
95 public Vector2 PositionRelativeTo(RectangleF frame,
bool skipOffset =
false)
99 return RelativePosition * frame.Size;
102 return frame.Location + RelativePosition * frame.Size;
105 public Vector2 SizeRelativeTo(RectangleF frame)
107 return RelativeSize * frame.Size;
110 public RectangleF RectangleRelativeTo(RectangleF frame,
bool skipOffset =
false)
112 return new RectangleF(PositionRelativeTo(frame, skipOffset), SizeRelativeTo(frame));
115 public void Deconstruct(out
float posX, out
float posY, out
float sizeX, out
float sizeY)
117 posX = RelativePosition.X;
118 posY = RelativePosition.Y;
119 sizeX = RelativeSize.X;
120 sizeY = RelativeSize.Y;
124 internal readonly
struct MiniMapSettings
126 public static MiniMapSettings
Default =
new MiniMapSettings
128 createHullElements:
true,
129 elementColor: MiniMap.MiniMapBaseColor
132 public readonly
bool CreateHullElements;
133 public readonly Color ElementColor;
135 public MiniMapSettings(
bool createHullElements =
false, Color? elementColor =
null)
137 CreateHullElements = createHullElements;
138 ElementColor = elementColor ?? MiniMap.MiniMapBaseColor;
142 partial class MiniMap : Powered
144 private Dictionary<Hull, HullData> hullDatas;
145 private DateTime resetDataTime;
147 private GUIFrame submarineContainer;
149 private GUIFrame? hullInfoFrame;
150 private GUIScissorComponent? scissorComponent;
151 private GUIComponent? miniMapContainer;
152 private GUIComponent miniMapFrame;
153 private GUIComponent electricalFrame;
154 private GUILayoutGroup reportFrame;
155 private GUILayoutGroup searchBarFrame;
156 private GUITextBox searchBar;
157 private GUIComponent? searchAutoComplete;
159 private ItemPrefab? searchedPrefab;
161 private GUITextBlock tooltipHeader, tooltipFirstLine, tooltipSecondLine, tooltipThirdLine;
163 private LocalizedString noPowerTip =
string.Empty;
165 private readonly List<Submarine> displayedSubs =
new List<Submarine>();
167 private Point prevResolution;
168 private float cardRefreshTimer;
169 private const float cardRefreshDelay = 3f;
171 private readonly HashSet<MiniMapSprite> cardsToDraw =
new HashSet<MiniMapSprite>();
173 private List<MapEntity> subEntities =
new List<MapEntity>();
175 private Texture2D? submarinePreview;
177 private MiniMapMode currentMode;
178 private ImmutableArray<GUIButton> modeSwitchButtons;
180 private Point elementSize;
182 private ImmutableDictionary<MapEntity, MiniMapGUIComponent> hullStatusComponents;
183 private ImmutableDictionary<MapEntity, MiniMapGUIComponent> electricalMapComponents;
184 private ImmutableDictionary<MiniMapGUIComponent, GUIComponent> electricalChildren;
185 private ImmutableDictionary<MiniMapGUIComponent, GUIComponent> doorChildren;
186 private ImmutableDictionary<MiniMapGUIComponent, GUIComponent> weaponChildren;
188 private ImmutableHashSet<ItemPrefab>? itemsFoundOnSub;
190 private ImmutableHashSet<Vector2>? MiniMapBlips;
191 private float blipState;
192 private const float maxBlipState = 1f;
194 private const float maxZoom = 10f,
198 private float zoom = defaultZoom;
203 set => zoom = Math.Clamp(value, minZoom, maxZoom);
206 private Vector2 mapOffset = Vector2.Zero;
207 private bool dragMap;
208 private Vector2? dragMapStart;
209 private const int dragTreshold = 8;
211 private bool recalculate;
215 private static readonly Color WetHullColor =
new Color(11, 122, 205),
216 DoorIndicatorColor = GUIStyle.Green,
217 NoPowerDoorColor = DoorIndicatorColor * 0.1f,
219 HoverColor = Color.White,
220 BlueprintBlue =
new Color(23, 38, 33),
221 HullWaterColor =
new Color(17, 173, 179) * 0.5f,
222 HullWaterLineColor = Color.LightBlue * 0.5f,
224 ElectricalBaseColor = GUIStyle.Orange,
225 NoPowerElectricalColor = ElectricalBaseColor * 0.1f;
227 partial
void InitProjSpecific()
229 hullDatas =
new Dictionary<Hull, HullData>();
233 noPowerTip = TextManager.Get(
"SteeringNoPowerTip");
237 private void SetDefaultMode()
239 currentMode =
true switch
244 _ => MiniMapMode.None
265 modeSwitchButtons = ImmutableArray.Create
267 new GUIButton(
new RectTransform(
new Vector2(0.25f, 1.0f), buttonLayout.
RectTransform),
string.Empty, style:
"StatusMonitorButton.HullStatus") { UserData = MiniMapMode.HullStatus, Enabled = EnableHullStatus, ToolTip = TextManager.Get(
"StatusMonitorButton.HullStatus.Tooltip") },
268 new GUIButton(
new RectTransform(
new Vector2(0.25f, 1.0f), buttonLayout.
RectTransform),
string.Empty, style:
"StatusMonitorButton.ElectricalView") { UserData = MiniMapMode.ElectricalView, Enabled = EnableElectricalView, ToolTip = TextManager.Get(
"StatusMonitorButton.ElectricalView.Tooltip") },
269 new GUIButton(
new RectTransform(
new Vector2(0.25f, 1.0f), buttonLayout.
RectTransform),
string.Empty, style:
"StatusMonitorButton.ItemFinder") { UserData = MiniMapMode.ItemFinder, Enabled = EnableItemFinder, ToolTip = TextManager.Get(
"StatusMonitorButton.ItemFinder.Tooltip") }
272 foreach (
GUIButton button
in modeSwitchButtons)
276 if (!(o is MiniMapMode m)) {
return false; }
280 mapOffset = Vector2.Zero;
283 foreach (
GUIButton otherButton
in modeSwitchButtons)
292 if (button.
UserData is MiniMapMode buttonMode)
294 button.
Selected = currentMode == buttonMode;
300 GUIFrame bottomFrame =
new GUIFrame(
new RectTransform(
new Vector2(0.5f, 0.15f), paddedContainer.RectTransform,
Anchor.BottomCenter) { MaxSize = new Point(int.MaxValue, GUI.IntScale(40)) }, style:
null)
308 AbsoluteSpacing = GUI.IntScale(5)
320 searchBar =
new GUITextBox(
new RectTransform(
new Vector2(1), searchBarFrame.RectTransform),
string.Empty, createClearButton:
true, createPenIcon:
true)
322 OnEnterPressed = (box, text) =>
335 SetAutoCompletePosition(searchAutoComplete, searchBar);
339 PlaySoundOnSelect =
true,
340 OnSelected = (component, o) =>
344 searchedPrefab = prefab;
345 searchBar.TextBlock.Text = prefab.Name;
347 SearchItems(searchBar.Text);
353 List<ItemPrefab> shownItemPrefabs =
new List<ItemPrefab>();
357 if (shownItemPrefabs.Any(ip => DisplayAsSameItem(ip, prefab)))
361 CreateItemFrame(prefab, listBox.Content.RectTransform);
362 shownItemPrefabs.Add(prefab);
365 searchBar.OnDeselected += (sender, key) =>
367 searchAutoComplete.Visible =
false;
370 searchBar.OnSelected += (sender, key) =>
372 itemsFoundOnSub =
Item.
ItemList.Where(it => VisibleOnItemFinder(it)).
Select(it => it.Prefab).ToImmutableHashSet();
375 searchBar.OnKeyHit += ControlSearchTooltip;
376 searchBar.OnTextChanged += UpdateSearchTooltip;
378 hullInfoFrame =
new GUIFrame(
new RectTransform(
new Vector2(0.13f, 0.13f), GUI.Canvas, minSize:
new Point(250, 150)), style:
"GUIToolTip")
380 CanBeFocused =
false,
387 RelativeSpacing = 0.05f
390 tooltipHeader =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.4f), hullInfoContainer.RectTransform),
string.Empty) { Wrap =
true };
391 tooltipFirstLine =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform),
string.Empty) { Wrap =
true };
392 tooltipSecondLine =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform),
string.Empty) { Wrap =
true };
393 tooltipThirdLine =
new GUITextBlock(
new RectTransform(
new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform),
string.Empty) { Wrap =
true };
397 c.CanBeFocused =
false;
398 c.Children.ForEach(c2 => c2.CanBeFocused =
false);
401 submarineBack.RectTransform.MaxSize =
402 submarineFront.RectTransform.MaxSize =
403 submarineContainer.RectTransform.MaxSize =
404 new Point(
int.MaxValue, paddedContainer.Rect.Height - bottomFrame.Rect.Height - buttonLayout.Rect.Height);
418 if (prefab1 == prefab2) {
return true; }
421 var sprite1 = GetPreviewSprite(prefab1);
422 var sprite2 = GetPreviewSprite(prefab2);
423 return sprite1?.FullPath == sprite2?.FullPath && sprite1?.SourceRect == sprite2?.SourceRect;
428 private bool VisibleOnItemFinder(
Item it)
430 if (it?.Submarine ==
null) {
return false; }
432 if (it.NonInteractable || it.IsHidden) {
return false; }
433 if (it.GetComponent<Pickable>() ==
null) {
return false; }
435 var holdable = it.GetComponent<Holdable>();
436 if (holdable !=
null && holdable.Attached) {
return false; }
438 var wire = it.GetComponent<Wire>();
439 if (wire !=
null && wire.Connections.Any(c => c !=
null)) {
return false; }
441 if (it.Container?.GetComponent<ItemContainer>() is { DrawInventory:
false } or { AllowAccess:
false }) {
return false; }
443 if (it.HasTag(Tags.TraitorMissionItem)) {
return false; }
450 base.AddToGUIUpdateList(order);
451 hullInfoFrame?.AddToGUIUpdateList(order: order + 1);
452 if (currentMode == MiniMapMode.ItemFinder && searchBar.Selected)
454 searchAutoComplete?.AddToGUIUpdateList(order: order + 1);
458 private void CreateHUD()
462 submarineContainer.ClearChildren();
466 displayedSubs.Clear();
471 miniMapContainer =
new GUIFrame(
new RectTransform(Vector2.One, scissorComponent.Content.RectTransform,
Anchor.Center), style:
null) { CanBeFocused =
false };
473 ImmutableHashSet<Item> hullPointsOfInterest =
Item.
ItemList.Where(it =>
item.
Submarine.
IsEntityFoundOnThisSub(it, includingConnectedSubs:
true) && !it.IsHidden && !it.NonInteractable && it.Prefab.ShowInStatusMonitor && (it.GetComponent<Door>() !=
null || it.GetComponent<Turret>() !=
null)).ToImmutableHashSet();
474 miniMapFrame =
CreateMiniMap(
item.
Submarine, submarineContainer, MiniMapSettings.Default, hullPointsOfInterest, out hullStatusComponents);
477 electricalFrame =
CreateMiniMap(
item.
Submarine, miniMapContainer,
new MiniMapSettings(createHullElements:
false), electricalPointsOfInterest, out electricalMapComponents);
479 Dictionary<MiniMapGUIComponent, GUIComponent> electricChildren =
new Dictionary<MiniMapGUIComponent, GUIComponent>();
481 foreach (var (entity, component) in electricalMapComponents)
484 if (entity is not
Item it ) {
continue; }
485 Sprite? sprite = it.Prefab.UpgradePreviewSprite;
486 if (sprite is
null) {
continue; }
488 GUIImage child =
new GUIImage(
new RectTransform(Vector2.One, parent.
RectTransform,
Anchor.Center), sprite)
490 OutlineColor = ElectricalBaseColor,
491 Color = ElectricalBaseColor,
496 electricChildren.Add(component, child);
499 electricalChildren = electricChildren.ToImmutableDictionary();
501 Dictionary<MiniMapGUIComponent, GUIComponent> doorChilds =
new Dictionary<MiniMapGUIComponent, GUIComponent>();
502 Dictionary<MiniMapGUIComponent, GUIComponent> weaponChilds =
new Dictionary<MiniMapGUIComponent, GUIComponent>();
504 foreach (var (entity, component) in hullStatusComponents)
506 if (!hullPointsOfInterest.Contains(entity)) {
continue; }
508 if (entity is not
Item it) {
continue; }
509 const int borderMaxSize = 2;
511 if (it.GetComponent<Door>() is { })
513 const int minSize = 8;
515 Point size = component.BorderComponent.Rect.Size;
517 size.X = Math.Max(size.X, minSize);
518 size.Y = Math.Max(size.Y, minSize);
519 float width = Math.Min(borderMaxSize, Math.Min(size.X, size.Y) / 8f);
521 GUIFrame frame =
new GUIFrame(
new RectTransform(size, component.RectComponent.RectTransform, anchor:
Anchor.Center), style:
"ScanLines", color: DoorIndicatorColor)
523 OutlineColor = DoorIndicatorColor,
524 OutlineThickness = width
526 doorChilds.Add(component, frame);
528 else if (it.GetComponent<Turret>() is { } turret)
530 int parentWidth = (int) (submarineContainer.Rect.Width / 16f);
531 GUICustomComponent frame =
new GUICustomComponent(
new RectTransform(
new Point(parentWidth, parentWidth), component.RectComponent.RectTransform, anchor:
Anchor.Center), (batch, customComponent) =>
533 Vector2 center = customComponent.Center;
534 float rotation = turret.Rotation;
538 float minRotation = MathHelper.ToRadians(Math.Min(turret.RotationLimits.X, turret.RotationLimits.Y)),
539 maxRotation = MathHelper.ToRadians(Math.Max(turret.RotationLimits.X, turret.RotationLimits.Y));
541 rotation = (minRotation + maxRotation) / 2;
544 if (turret.WeaponIndicatorSprite is { } weaponSprite)
546 Vector2 origin = weaponSprite.Origin;
547 float scale = parentWidth / Math.Max(weaponSprite.size.X, weaponSprite.size.Y);
548 Color color = !hasPower ? NoPowerColor : turret.ActiveUser is null ? Color.DimGray : GUIStyle.Green;
549 weaponSprite.Draw(batch, center, color, origin, rotation, scale, SpriteEffects.None);
556 weaponChilds.Add(component, frame);
560 doorChildren = doorChilds.ToImmutableDictionary();
561 weaponChildren = weaponChilds.ToImmutableDictionary();
563 Rectangle parentRect = miniMapFrame.Rect;
565 displayedSubs.Clear();
569 subEntities = MapEntity.MapEntityList.Where(me => (
item.
Submarine is { } sub && sub.
IsEntityFoundOnThisSub(me, includingConnectedSubs:
true, allowDifferentType:
false)) && !me.IsHidden).OrderByDescending(w => w.SpriteDepth).ToList();
578 if (item.Submarine ==
null && displayedSubs.Count > 0 ||
579 item.Submarine is { } itemSub &&
581 !displayedSubs.Contains(itemSub) ||
582 itemSub.DockedTo.Where(s => s.TeamID == item.Submarine.TeamID).Any(s => !displayedSubs.Contains(s) && itemSub.ConnectedDockingPorts[s].IsLocked) ||
583 displayedSubs.Any(s => s != itemSub && !itemSub.DockedTo.Contains(s))
586 !submarineContainer.Children.Any())
593 if (DateTime.Now > resetDataTime)
595 foreach (HullData hullData
in hullDatas.Values)
597 if (!hullData.Distort)
599 if (Timing.TotalTime > hullData.LastOxygenDataTime + 1.0) { hullData.ReceivedOxygenAmount =
null; }
600 if (Timing.TotalTime > hullData.LastWaterDataTime + 1.0) { hullData.ReceivedWaterAmount =
null; }
603 resetDataTime = DateTime.Now +
new TimeSpan(0, 0, 1);
606 if (cardRefreshTimer > cardRefreshDelay)
608 if (item.Submarine is { } sub)
612 cardRefreshTimer = 0;
616 cardRefreshTimer += deltaTime;
619 if (scissorComponent !=
null)
623 if (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn))
629 if (currentMode != MiniMapMode.HullStatus && Math.Abs(
PlayerInput.
ScrollWheelSpeed) > 0 && (GUI.MouseOn == scissorComponent || scissorComponent.IsParentOf(GUI.MouseOn)))
632 float distanceScale = newZoom / Zoom;
633 mapOffset *= distanceScale;
634 recalculate |= !MathUtils.NearlyEqual(Zoom, newZoom);
639 if (dragMapStart is { } dragStart)
641 if (dragMap || Vector2.DistanceSquared(dragStart,
PlayerInput.
MousePosition) > GUI.IntScale(dragTreshold * dragTreshold))
659 if (miniMapContainer !=
null)
661 miniMapContainer.RectTransform.LocalScale =
new Vector2(Zoom);
662 miniMapContainer.RectTransform.RecalculateChildren(
true,
true);
663 miniMapContainer.RectTransform.AbsoluteOffset = mapOffset.ToPoint();
669 if (GuiFrame.Rect.Size != elementSize)
672 elementSize = GuiFrame.Rect.Size;
675 float distort = item.Repairables.Any(r => r.IsBelowRepairThreshold) ? 1.0f - item.Condition / item.MaxCondition : 0.0f;
676 foreach (HullData hullData
in hullDatas.Values)
678 hullData.DistortionTimer -= deltaTime;
679 if (hullData.DistortionTimer <= 0.0f)
681 hullData.Distort = Rand.Range(0.0f, 1.0f) < distort * distort;
682 if (hullData.Distort)
684 hullData.ReceivedOxygenAmount = Rand.Range(0.0f, 100.0f);
685 hullData.ReceivedWaterAmount = Rand.Range(0.0f, 100.0f);
687 hullData.DistortionTimer = Rand.Range(1.0f, 10.0f);
693 if (blipState > maxBlipState)
698 blipState += deltaTime;
700 if (currentMode == MiniMapMode.HullStatus && !EnableHullStatus ||
701 currentMode == MiniMapMode.ElectricalView && !EnableElectricalView ||
702 currentMode == MiniMapMode.ItemFinder && !EnableItemFinder)
707 modeSwitchButtons[0].Enabled = EnableHullStatus;
708 modeSwitchButtons[1].Enabled = EnableElectricalView;
709 modeSwitchButtons[2].Enabled = EnableItemFinder;
712 private void UpdateIDCards(
Submarine sub)
714 if (hullDatas is
null) {
return; }
716 foreach (HullData data
in hullDatas.Values)
723 if (it is { CurrentHull: { } hull } && it.GetComponent<IdCard>() is { } idCard && idCard.TeamID == sub.
TeamID)
725 if (!hullDatas.ContainsKey(hull)) {
continue; }
727 hullDatas[hull].Cards.Add(idCard);
732 private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container)
734 if (miniMapFrame ==
null)
740 if (Voltage < MinVoltage)
742 Vector2 textSize = GUIStyle.Font.MeasureString(noPowerTip);
743 Vector2 textPos = GuiFrame.Rect.Center.ToVector2();
744 Color noPowerColor = GUIStyle.Orange * (float)Math.Abs(Math.Sin(Timing.TotalTime));
746 GUI.DrawString(spriteBatch, textPos - textSize / 2, noPowerTip, noPowerColor, Color.Black * 0.8f, font: GUIStyle.SubHeadingFont);
750 if (currentMode == MiniMapMode.HullStatus && item.Submarine !=
null)
752 Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
754 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
755 spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect;
757 var sprite = GUIStyle.UIGlowSolidCircular.Value?.Sprite;
758 float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f;
759 if (sprite !=
null && ShowHullIntegrity)
761 Vector2 spriteSize = sprite.size;
762 Rectangle worldBorders = item.Submarine.GetDockedBorders(allowDifferentTeam:
false);
763 worldBorders.Location += item.Submarine.WorldPosition.ToPoint();
764 foreach (Gap gap
in Gap.GapList)
766 if (gap.IsRoomToRoom || gap.linkedTo.Count == 0 || gap.Submarine != item.Submarine || gap.ConnectedDoor !=
null || gap.IsHidden) {
continue; }
767 RectangleF entityRect = ScaleRectToUI(gap, miniMapFrame.Rect, worldBorders);
769 Vector2 scale =
new Vector2(entityRect.Size.X / spriteSize.X, entityRect.Size.Y / spriteSize.Y) * 2.0f;
771 Color color = ToolBox.GradientLerp(gap.Open, GUIStyle.HealthBarColorMedium, GUIStyle.HealthBarColorLow) * alpha;
772 sprite.Draw(spriteBatch,
773 miniMapFrame.Rect.Location.ToVector2() + entityRect.Center,
774 color, origin: sprite.Origin, rotate: 0.0f, scale: scale);
778 if (currentMode == MiniMapMode.HullStatus && hullStatusComponents !=
null)
780 foreach (var (entity, component) in hullStatusComponents)
782 if (!(entity is Hull hull)) {
continue; }
783 if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is
null) {
continue; }
784 DrawHullCards(spriteBatch, hull, hullData, component.RectComponent);
786 if (item.CurrentHull is { } currentHull && currentHull == hull)
788 Sprite? pingCircle = GUIStyle.YouAreHereCircle.Value?.Sprite;
789 if (pingCircle is
null) {
continue; }
791 Vector2 charPos = item.WorldPosition;
792 Vector2 hullPos = hull.WorldRect.Location.ToVector2(),
793 hullSize = hull.WorldRect.Size.ToVector2();
794 Vector2 relativePos = (charPos - hullPos) / hullSize * component.RectComponent.Rect.Size.ToVector2();
795 relativePos.Y = -relativePos.Y;
797 float parentWidth = submarineContainer.Rect.Width / 64f;
798 float spriteSize = pingCircle.size.X * (parentWidth / pingCircle.size.X);
800 Vector2 drawPos = component.RectComponent.Rect.Location.ToVector2() + relativePos;
801 drawPos -=
new Vector2(spriteSize, spriteSize) / 2f;
803 pingCircle.Draw(spriteBatch, drawPos, GUIStyle.Red * 0.8f, Vector2.Zero, 0f, parentWidth / pingCircle.size.X);
809 spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
810 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
814 private void ControlSearchTooltip(GUITextBox sender, Keys key)
816 if (searchAutoComplete is
null || !searchAutoComplete.Visible) {
return; }
817 GUIListBox listBox = searchAutoComplete.GetChild<GUIListBox>();
818 if (listBox is
null) {
return; }
820 if (key == Keys.Down)
822 listBox.SelectNext(force: GUIListBox.Force.Yes, playSelectSound: GUIListBox.PlaySelectSound.Yes);
824 else if (key == Keys.Up)
826 listBox.SelectPrevious(force: GUIListBox.Force.Yes, playSelectSound: GUIListBox.PlaySelectSound.Yes);
828 else if (key == Keys.Enter)
830 listBox.OnSelected?.Invoke(listBox, listBox.SelectedData);
831 searchBar.Deselect();
835 private bool UpdateSearchTooltip(GUITextBox box,
string? text)
837 if (text is
null || itemsFoundOnSub is
null || searchAutoComplete is
null) {
return false; }
840 searchedPrefab =
null;
841 searchAutoComplete.Visible =
true;
842 SetAutoCompletePosition(searchAutoComplete, box);
844 GUIListBox? listBox = searchAutoComplete.GetChild<GUIListBox>();
845 if (listBox?.Content is
null) {
return false; }
851 foreach (GUIComponent component
in listBox.Content.Children)
853 component.Visible =
false;
854 if (component.UserData is ItemPrefab { Name: { } prefabName} prefab &&
855 (itemsFoundOnSub.Contains(prefab) || itemsFoundOnSub.Any(ip => DisplayAsSameItem(ip, prefab))))
857 component.Visible = prefabName.ToLower().Contains(text.ToLower());
859 if (component.Visible && first)
861 listBox.Select(i, GUIListBox.Force.Yes, GUIListBox.AutoScroll.Disabled);
869 listBox.BarScroll = 0f;
870 listBox.RecalculateChildren();
875 private void SetAutoCompletePosition(GUIComponent tooltip, GUITextBox box)
877 int height = GuiFrame.Rect.Height / 2;
878 tooltip.RectTransform.NonScaledSize =
new Point(box.Rect.Width, height);
879 tooltip.RectTransform.ScreenSpaceOffset =
new Point(box.Rect.X, box.Rect.Y - height);
882 private static void CreateItemFrame(ItemPrefab prefab, RectTransform parent)
884 Sprite sprite = GetPreviewSprite(prefab);
885 if (sprite is
null) {
return; }
886 GUIFrame frame =
new GUIFrame(
new RectTransform(
new Vector2(1f, 0.25f), parent), style:
"ListBoxElement")
891 GUILayoutGroup layout =
new GUILayoutGroup(
new RectTransform(Vector2.One, frame.RectTransform), isHorizontal:
true)
895 new GUIImage(
new RectTransform(Vector2.One, layout.RectTransform, scaleBasis:
ScaleBasis.BothHeight), sprite)
897 Color = prefab.InventoryIconColor,
901 var nameText =
new GUITextBlock(
new RectTransform(Vector2.One, layout.RectTransform), prefab.Name);
902 nameText.RectTransform.SizeChanged += () =>
904 nameText.Text = ToolBox.LimitString(prefab.Name, nameText.Font, nameText.Rect.Width);
908 private void SearchItems(
string text)
910 if (searchedPrefab is
null)
912 ItemPrefab? first = ItemPrefab.Prefabs.FirstOrDefault(p => p.Name.ToLower().Equals(text.ToLower()));
916 searchBar.Flash(GUIStyle.Red);
919 searchedPrefab = first;
922 if (item.Submarine is
null) {
return; }
924 HashSet<Item> foundItems =
new HashSet<Item>();
926 foreach (Item it
in Item.ItemList)
928 if (!VisibleOnItemFinder(it)) {
continue; }
930 if (DisplayAsSameItem(it.Prefab, searchedPrefab))
933 if (it.FindParentInventory(inv => inv is CharacterInventory || inv is ItemInventory { Owner: Item { IsHidden: true }}) is { }) {
continue; }
935 if (it.FindParentInventory(inventory => inventory is ItemInventory { Owner: Item { ParentInventory: null } }) is ItemInventory parent)
937 foundItems.Add((Item)parent.Owner);
947 RectangleF dockedBorders = item.Submarine.GetDockedBorders(allowDifferentTeam:
false);
948 dockedBorders.Location += item.Submarine.WorldPosition;
949 RectangleF parentRect = miniMapFrame.Rect;
951 HashSet<Vector2> positions =
new HashSet<Vector2>();
952 foreach (Item foundItem
in foundItems)
954 RelativeEntityRect scaledRect =
new RelativeEntityRect(dockedBorders, foundItem.WorldRect);
955 Vector2 pos = scaledRect.PositionRelativeTo(parentRect, skipOffset:
true) + scaledRect.SizeRelativeTo(parentRect) / 2f;
959 MiniMapBlips = positions.ToImmutableHashSet();
961 if (searchAutoComplete is
null) {
return; }
962 searchAutoComplete.Visible =
false;
965 private void UpdateHUDBack()
967 if (item.Submarine ==
null) {
return; }
969 if (hullInfoFrame !=
null) { hullInfoFrame.Visible =
false; }
970 reportFrame.Visible =
false;
971 searchBarFrame.Visible =
false;
972 electricalFrame.Visible =
false;
973 miniMapFrame.Visible =
false;
977 case MiniMapMode.HullStatus:
979 miniMapFrame.Visible =
true;
980 reportFrame.Visible =
true;
982 case MiniMapMode.ElectricalView:
983 UpdateElectricalView();
984 electricalFrame.Visible =
true;
986 case MiniMapMode.ItemFinder:
987 searchBarFrame.Visible =
true;
992 private void UpdateHullStatus()
994 bool canHoverOverHull =
true;
996 foreach (var (entity, component) in hullStatusComponents)
999 if (entity is Hull) {
continue; }
1001 GUIComponent rectComponent = component.RectComponent;
1003 if (doorChildren.TryGetValue(component, out GUIComponent? child) && child !=
null)
1005 if (item.Submarine ==
null || !hasPower)
1007 child.Color = child.OutlineColor = NoPowerDoorColor;
1010 if (Voltage < MinVoltage) {
continue; }
1012 child.Color = child.OutlineColor = DoorIndicatorColor;
1013 if (GUI.MouseOn == child)
1015 SetTooltip(rectComponent.Rect.Center, entity.Name,
string.Empty,
string.Empty,
string.Empty);
1016 canHoverOverHull =
false;
1017 child.Color = child.OutlineColor = HoverColor;
1022 foreach (var (entity, (component, borderComponent)) in hullStatusComponents)
1024 if (item.Submarine ==
null || !hasPower)
1026 component.Color = borderComponent.OutlineColor = NoPowerColor;
1029 if (!component.Visible) {
continue; }
1030 if (entity is not Hull hull) {
continue; }
1031 if (!submarineContainer.Rect.Contains(component.Rect))
1035 component.Visible = borderComponent.Visible =
false;
1040 if (Voltage < MinVoltage) {
continue; }
1042 hullDatas.TryGetValue(hull, out HullData? hullData);
1043 if (hullData is
null)
1045 hullData =
new HullData();
1046 GetLinkedHulls(hull, hullData.LinkedHulls);
1047 hullDatas.Add(hull, hullData);
1050 Color neutralColor = DefaultNeutralColor;
1051 Color borderColor = neutralColor;
1052 Color componentColor;
1056 neutralColor = WetHullColor;
1059 if (hullData.Distort)
1061 borderComponent.OutlineColor = neutralColor * 0.5f;
1062 component.Color = Color.Lerp(Color.Black, Color.DarkGray * 0.5f, Rand.Range(0.0f, 1.0f));
1066 if (RequireOxygenDetectors)
1068 hullData.HullOxygenAmount = hullData.ReceivedOxygenAmount;
1070 else if (hullData.LinkedHulls.Any())
1072 hullData.HullOxygenAmount = 0.0f;
1073 foreach (Hull linkedHull
in hullData.LinkedHulls)
1075 hullData.HullOxygenAmount += linkedHull.OxygenPercentage;
1077 hullData.HullOxygenAmount /= hullData.LinkedHulls.Count;
1081 hullData.HullOxygenAmount = hull.OxygenPercentage;
1083 if (RequireWaterDetectors)
1085 hullData.HullWaterAmount = hullData.ReceivedWaterAmount;
1087 else if (hullData.LinkedHulls.Any())
1089 float waterVolume = 0.0f;
1090 float totalVolume = 0.0f;
1091 foreach (Hull linkedHull
in hullData.LinkedHulls)
1095 if (WaterDetector.GetWaterPercentage(linkedHull) > 0.0f)
1097 waterVolume += linkedHull.WaterVolume;
1099 totalVolume += linkedHull.Volume;
1101 hullData.HullWaterAmount =
1102 waterVolume > 1.0f ?
1103 MathHelper.Clamp((
int)Math.Ceiling(waterVolume / totalVolume * 100), 0, 100) : 0.0f;
1107 hullData.HullWaterAmount = WaterDetector.GetWaterPercentage(hull);
1110 float gapOpenSum = 0.0f;
1112 if (ShowHullIntegrity)
1114 float amount = 1f + hullData.LinkedHulls.Count;
1115 gapOpenSum = hull.ConnectedGaps.Concat(hullData.LinkedHulls.SelectMany(h => h.ConnectedGaps)).Where(g => g.linkedTo.Count == 1 && !g.IsHidden).Sum(g => g.Open) / amount;
1116 borderColor = Color.Lerp(neutralColor, GUIStyle.Red, Math.Min(gapOpenSum, 1.0f));
1119 bool isHoveringOver = canHoverOverHull && GUI.MouseOn == component;
1124 LocalizedString header = hull.DisplayName;
1126 float? oxygenAmount = hullData.HullOxygenAmount,
1127 waterAmount = hullData.HullWaterAmount;
1129 LocalizedString line1 = gapOpenSum > 0.1f ? TextManager.Get(
"MiniMapHullBreach") :
string.Empty;
1130 Color line1Color = GUIStyle.Red;
1132 LocalizedString line2 = oxygenAmount ==
null ?
1133 TextManager.Get(
"MiniMapAirQualityUnavailable") :
1134 TextManager.AddPunctuation(
':', TextManager.Get(
"MiniMapAirQuality"), (
int)Math.Round(oxygenAmount.Value) +
"%");
1135 Color line2Color = oxygenAmount ==
null ? GUIStyle.Red : Color.Lerp(GUIStyle.Red, Color.LightGreen, (
float)oxygenAmount / 100.0f);
1137 LocalizedString line3 = waterAmount ==
null ?
1138 TextManager.Get(
"MiniMapWaterLevelUnavailable") :
1139 TextManager.AddPunctuation(
':', TextManager.Get(
"MiniMapWaterLevel"), (
int)Math.Round(waterAmount.Value) +
"%");
1140 Color line3Color = waterAmount ==
null ? GUIStyle.Red : Color.Lerp(Color.LightGreen, GUIStyle.Red, (
float)waterAmount / 100.0f);
1142 SetTooltip(borderComponent.Rect.Center, header, line1, line2, line3, line1Color, line2Color, line3Color);
1145 bool draggingReport = GameMain.GameSession?.CrewManager?.DraggedOrderPrefab !=
null;
1147 foreach (Hull linkedHull
in hullData.LinkedHulls)
1149 if (!hullStatusComponents.ContainsKey(linkedHull)) {
continue; }
1153 (hullStatusComponents[linkedHull].RectComponent == GUI.MouseOn || (draggingReport && hullStatusComponents[linkedHull].RectComponent.MouseRect.Contains(PlayerInput.MousePosition)));
1154 if (isHoveringOver) {
break; }
1157 if (isHoveringOver || (draggingReport && component.MouseRect.Contains(PlayerInput.MousePosition)))
1159 borderColor = Color.Lerp(borderColor, Color.White, 0.5f);
1160 componentColor = HoverColor;
1164 componentColor = neutralColor * 0.8f;
1167 borderComponent.OutlineColor = borderColor;
1168 component.Color = componentColor;
1172 private void UpdateElectricalView()
1174 foreach (var (entity, miniMapGuiComponent) in electricalMapComponents)
1176 if (entity is not Item it) {
continue; }
1177 if (!electricalChildren.TryGetValue(miniMapGuiComponent, out GUIComponent? component)) {
continue; }
1181 component.Visible =
false;
1185 if (item.Submarine ==
null || !hasPower)
1187 component.Color = component.OutlineColor = NoPowerElectricalColor;
1190 if (Voltage < MinVoltage || !miniMapGuiComponent.RectComponent.Visible) {
continue; }
1192 int durability = (int)(it.Condition / (it.MaxCondition / it.MaxRepairConditionMultiplier) * 100f);
1193 Color color = ToolBox.GradientLerp(durability / 100f, GUIStyle.Red, GUIStyle.Orange, GUIStyle.Green, GUIStyle.Green);
1195 if (GUI.MouseOn == component)
1197 LocalizedString line1 =
string.Empty;
1198 LocalizedString line2 =
string.Empty;
1200 if (it.GetComponent<PowerContainer>() is { } battery)
1202 int batteryCapacity = (int)(battery.Charge / battery.GetCapacity() * 100f);
1203 line2 = TextManager.GetWithVariable(
"statusmonitor.battery.tooltip",
"[amount]", batteryCapacity.ToString());
1205 else if (it.GetComponent<PowerTransfer>() is { } powerTransfer)
1207 int current = 0, load = 0;
1208 if (powerTransfer.PowerConnections.Count > 0 && powerTransfer.PowerConnections[0].Grid !=
null)
1210 current = (int)powerTransfer.PowerConnections[0].Grid.Power;
1211 load = (
int)powerTransfer.PowerConnections[0].Grid.Load;
1214 line1 = TextManager.GetWithVariable(
"statusmonitor.junctionpower.tooltip",
"[amount]", current.ToString())
1215 .Fallback(TextManager.GetWithVariable(
"statusmonitor.junctioncurrent.tooltip",
"[amount]", current.ToString()));
1216 line2 = TextManager.GetWithVariables(
"statusmonitor.junctionload.tooltip",
1217 (
"[amount]", load.ToString()),
1218 (
"[load]", load.ToString()));
1221 LocalizedString line3 = TextManager.GetWithVariable(
"statusmonitor.durability.tooltip",
"[amount]", durability.ToString());
1222 SetTooltip(component.Rect.Center, it.Prefab.Name, line1, line2, line3, line3Color: color);
1226 component.Color = component.OutlineColor = color;
1230 private void DrawHUDBack(SpriteBatch spriteBatch, GUICustomComponent container)
1232 if (item.Submarine ==
null) {
return; }
1234 DrawSubmarine(spriteBatch);
1236 if (Voltage < MinVoltage) {
return; }
1237 Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
1239 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
1240 spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect;
1242 if (currentMode == MiniMapMode.ItemFinder)
1244 if (MiniMapBlips !=
null)
1246 foreach (Vector2 blip
in MiniMapBlips)
1248 Vector2 parentSize = miniMapFrame.Rect.Size.ToVector2();
1249 Sprite? pingCircle = GUIStyle.PingCircle.Value?.Sprite;
1250 if (pingCircle is
null) {
continue; }
1251 Vector2 targetSize =
new Vector2(parentSize.X / 4f);
1252 Vector2 spriteScale = targetSize / pingCircle.size;
1253 float scale = Math.Min(blipState, maxBlipState / 2f);
1254 float alpha = 1.0f - Math.Clamp((blipState - maxBlipState * 0.25f) * 2f, 0f, 1f);
1255 pingCircle.Draw(spriteBatch, electricalFrame.Rect.Location.ToVector2() + blip * Zoom, GUIStyle.Red * alpha, pingCircle.Origin, 0f, spriteScale * scale, SpriteEffects.None);
1261 bool hullsVisible = currentMode == MiniMapMode.HullStatus && item.Submarine !=
null;
1263 if (hullStatusComponents !=
null)
1265 foreach (var (entity, component) in hullStatusComponents)
1267 if (entity is not Hull hull) {
continue; }
1268 if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is
null) {
continue; }
1270 if (hullData.Distort) {
continue; }
1272 GUIComponent hullFrame = component.RectComponent;
1274 if (hullsVisible && hullData.HullWaterAmount is { } waterAmount)
1276 if (!RequireWaterDetectors) { waterAmount = WaterDetector.GetWaterPercentage(hull); }
1277 waterAmount /= 100.0f;
1278 if (hullFrame.Rect.Height * waterAmount > 1.0f)
1280 RectangleF waterRect =
new RectangleF(hullFrame.Rect.X, hullFrame.Rect.Y + hullFrame.Rect.Height * (1.0f - waterAmount), hullFrame.Rect.Width, hullFrame.Rect.Height * waterAmount);
1282 const float width = 1f;
1284 GUI.DrawFilledRectangle(spriteBatch, waterRect, HullWaterColor);
1286 if (!MathUtils.NearlyEqual(waterAmount, 1.0f))
1288 Vector2 offset =
new Vector2(0, width);
1289 GUI.DrawLine(spriteBatch, waterRect.Location + offset,
new Vector2(waterRect.Right, waterRect.Y) + offset, HullWaterLineColor, width: width);
1294 if (hullsVisible && hullData.HullOxygenAmount is { } oxygenAmount)
1296 GUI.DrawRectangle(spriteBatch, hullFrame.Rect, Color.Lerp(GUIStyle.Red * 0.5f, GUIStyle.Green * 0.3f, oxygenAmount / 100.0f),
true);
1303 spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
1304 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
1307 private void SetTooltip(Point pos, LocalizedString header, LocalizedString line1, LocalizedString line2, LocalizedString line3, Color? line1Color =
null, Color? line2Color =
null, Color? line3Color =
null)
1309 if (hullInfoFrame ==
null) {
return; }
1310 hullInfoFrame.RectTransform.ScreenSpaceOffset = pos;
1312 if (hullInfoFrame.Rect.Left > submarineContainer.Rect.Right) { hullInfoFrame.RectTransform.ScreenSpaceOffset =
new Point(submarineContainer.Rect.Right, hullInfoFrame.RectTransform.ScreenSpaceOffset.Y); }
1313 if (hullInfoFrame.Rect.Top > submarineContainer.Rect.Bottom) { hullInfoFrame.RectTransform.ScreenSpaceOffset =
new Point(hullInfoFrame.RectTransform.ScreenSpaceOffset.X, submarineContainer.Rect.Bottom); }
1315 if (hullInfoFrame.Rect.Right > GameMain.GraphicsWidth) { hullInfoFrame.RectTransform.ScreenSpaceOffset -=
new Point(hullInfoFrame.Rect.Width, 0); }
1316 if (hullInfoFrame.Rect.Bottom > GameMain.GraphicsHeight) { hullInfoFrame.RectTransform.ScreenSpaceOffset -=
new Point(0, hullInfoFrame.Rect.Height); }
1318 hullInfoFrame.Visible =
true;
1319 tooltipHeader.Text = header;
1321 tooltipFirstLine.Text = line1;
1322 tooltipFirstLine.TextColor = line1Color ?? GUIStyle.TextColorNormal;
1324 tooltipSecondLine.Text = line2;
1325 tooltipSecondLine.TextColor = line2Color ?? GUIStyle.TextColorNormal;
1327 tooltipThirdLine.Text = line3;
1328 tooltipThirdLine.TextColor = line3Color ?? GUIStyle.TextColorNormal;
1331 private void BakeSubmarine(Submarine sub, Rectangle container)
1333 submarinePreview?.Dispose();
1334 Rectangle parentRect =
new Rectangle(container.X, container.Y, container.Width, container.Height);
1335 const int inflate = 128;
1336 parentRect.Inflate(inflate, inflate);
1337 RenderTarget2D rt =
new RenderTarget2D(GameMain.Instance.GraphicsDevice, parentRect.Width, parentRect.Height,
false, SurfaceFormat.Color, DepthFormat.None);
1339 using SpriteBatch spriteBatch =
new SpriteBatch(GameMain.Instance.GraphicsDevice);
1340 GameMain.Instance.GraphicsDevice.SetRenderTarget(rt);
1341 GameMain.Instance.GraphicsDevice.Clear(Color.Transparent);
1342 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
1343 Rectangle worldBorders = sub.GetDockedBorders(allowDifferentTeam:
false);
1344 worldBorders.Location += sub.WorldPosition.ToPoint();
1346 parentRect.Inflate(-inflate, -inflate);
1348 foreach (MapEntity entity
in subEntities)
1350 if (entity is Structure wall)
1352 if (wall.IsPlatform) {
continue; }
1353 DrawStructure(spriteBatch, wall, parentRect, worldBorders, inflate);
1356 if (entity is Item it)
1358 if (it.GetComponent<Pickable>() !=
null || it.ParentInventory !=
null) {
continue; }
1359 DrawItem(spriteBatch, it, parentRect, worldBorders, inflate);
1364 GameMain.Instance.GraphicsDevice.SetRenderTarget(
null);
1365 submarinePreview = rt;
1368 private void DrawSubmarine(SpriteBatch spriteBatch)
1370 Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
1372 if (submarinePreview is { } texture && miniMapContainer is { } mapContainer)
1374 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, blendState: BlendState.NonPremultiplied, effect: GameMain.GameScreen.BlueprintEffect, rasterizerState: GameMain.ScissorTestEnable);
1375 spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect;
1377 GameMain.GameScreen.BlueprintEffect.Parameters[
"width"].SetValue((
float)texture.Width);
1378 GameMain.GameScreen.BlueprintEffect.Parameters[
"height"].SetValue((
float)texture.Height);
1380 Color blueprintBlue = BlueprintBlue * currentMode
switch { MiniMapMode.HullStatus => 0.1f, MiniMapMode.ElectricalView => 0.1f, _ => 0.5f };
1382 Vector2 origin =
new Vector2(texture.Width / 2f, texture.Height / 2f);
1383 float scale = currentMode == MiniMapMode.HullStatus ? 1.0f : Zoom;
1384 spriteBatch.Draw(texture, mapContainer.Center,
null, blueprintBlue, 0f, origin, scale, SpriteEffects.None, 0f);
1388 spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
1389 spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
1392 private static void DrawItem(ISpriteBatch spriteBatch, Item item, Rectangle parent, Rectangle border,
int inflate)
1394 Sprite sprite = item.Sprite;
1395 if (sprite is
null) {
return; }
1397 RectangleF entityRect = ScaleRectToUI(item, parent, border);
1399 Vector2 spriteScale =
new Vector2(entityRect.Size.X / sprite.size.X, entityRect.Size.Y / sprite.size.Y);
1400 Vector2 origin =
new Vector2(sprite.Origin.X * spriteScale.X, sprite.Origin.Y * spriteScale.Y);
1402 if (!item.Prefab.ShowInStatusMonitor && item.GetComponent<Turret>() is { } turret)
1404 Vector2 drawPos = turret.GetDrawPos();
1405 drawPos.Y = -drawPos.Y;
1406 if (turret.BarrelSprite is { } barrelSprite)
1408 DrawAdditionalSprite(drawPos, barrelSprite, turret.Rotation + MathHelper.PiOver2);
1412 Vector2 pos = entityRect.Location + origin;
1416 sprite.Draw(spriteBatch, pos, item.SpriteColor, sprite.Origin, item.RotationRad, spriteScale, item.SpriteEffects);
1418 void DrawAdditionalSprite(Vector2 basePos, Sprite addSprite,
float rotation)
1420 RectangleF addRect = ScaleRectToUI(
new RectangleF(basePos, addSprite.size * item.Scale), parent, border);
1421 Vector2 addScale =
new Vector2(addRect.Size.X / addSprite.size.X, addRect.Size.Y / addSprite.size.Y);
1422 addSprite.Draw(spriteBatch,
new Vector2(addRect.Location.X + inflate, addRect.Location.Y + inflate), item.SpriteColor, addSprite.Origin, rotation, addScale, item.SpriteEffects);
1426 private static void DrawStructure(ISpriteBatch spriteBatch, Structure structure, Rectangle parent, Rectangle border,
int inflate)
1428 Sprite sprite = structure.Sprite;
1429 if (sprite is
null) {
return; }
1431 Vector2 textureOffset = structure.TextureOffset;
1432 textureOffset =
new Vector2(
1433 MathUtils.PositiveModulo(-textureOffset.X, sprite.SourceRect.Width * structure.TextureScale.X * structure.Scale),
1434 MathUtils.PositiveModulo(-textureOffset.Y, sprite.SourceRect.Height * structure.TextureScale.Y * structure.Scale));
1436 RectangleF entityRect = ScaleRectToUI(structure, parent, border);
1437 Vector2 spriteScale =
new Vector2(entityRect.Size.X / structure.Rect.Width, entityRect.Size.Y / structure.Rect.Height);
1438 float rotation = MathHelper.ToRadians(structure.Rotation);
1441 spriteBatch: spriteBatch,
1442 position: entityRect.Location + entityRect.Size * 0.5f + (inflate, inflate),
1443 targetSize: entityRect.Size,
1445 origin: entityRect.Size * 0.5f,
1446 color: structure.SpriteColor,
1447 startOffset: textureOffset * spriteScale,
1448 textureScale: structure.TextureScale * structure.Scale * spriteScale,
1449 depth: structure.SpriteDepth,
1450 spriteEffects: sprite.effects ^ structure.SpriteEffects);
1453 private static RectangleF ScaleRectToUI(MapEntity entity, RectangleF parentRect, RectangleF worldBorders)
1455 return ScaleRectToUI(entity.WorldRect, parentRect, worldBorders);
1458 private static RectangleF ScaleRectToUI(RectangleF rect, RectangleF parentRect, RectangleF worldBorders)
1460 RelativeEntityRect relativeRect =
new RelativeEntityRect(worldBorders, rect);
1461 return relativeRect.RectangleRelativeTo(parentRect, skipOffset:
true);
1464 private void DrawHullCards(SpriteBatch spriteBatch, Hull hull, HullData data, GUIComponent frame)
1466 cardsToDraw.Clear();
1468 if (GameMain.GameSession?.CrewManager is { ActiveOrders: { } orders })
1470 foreach (var activeOrder
in orders)
1472 Order order = activeOrder.Order;
1473 if (order is { SymbolSprite: { }, TargetEntity: Hull _ } && order.TargetEntity == hull)
1475 cardsToDraw.Add(
new MiniMapSprite(order));
1480 foreach (IdCard card
in data.Cards)
1482 if (card.OwnerJob is { Icon: { }} job)
1484 cardsToDraw.Add(
new MiniMapSprite(job));
1488 if (!cardsToDraw.Any()) {
return; }
1490 var (centerX, centerY) = frame.Center;
1492 const float padding = 8f;
1493 float totalWidth = 0f;
1495 float parentWidth = submarineContainer.Rect.Width / 24f;
1498 foreach (MiniMapSprite info
in cardsToDraw)
1500 if (info.Sprite is
null) {
continue; }
1502 float spriteSize = info.Sprite.size.X * (parentWidth / info.Sprite.size.X) + padding;
1503 if (totalWidth + spriteSize > frame.Rect.Width) {
break; }
1505 totalWidth += spriteSize;
1509 if (i > 0) { totalWidth -= padding; }
1511 float adjustedCenterX = centerX - totalWidth / 2f;
1516 foreach (MiniMapSprite info
in cardsToDraw)
1518 Sprite? sprite = info.Sprite;
1519 if (sprite is
null) {
continue; }
1520 float scale = parentWidth / sprite.size.X;
1521 float spriteSize = sprite.size.X * scale;
1522 float posX = adjustedCenterX + offset;
1524 if (posX + spriteSize > frame.Rect.X + frame.Rect.Width && amount > 0)
1526 int amountLeft = cardsToDraw.Count - amount;
1529 string text = $
"+{amountLeft}";
1530 var (sizeX, sizeY) = GUIStyle.SubHeadingFont.MeasureString(text);
1531 float maxWidth = Math.Max(sizeX, sizeY);
1532 Vector2 drawPos =
new Vector2(frame.Rect.Right - sizeX, frame.Rect.Y - sizeY / 2f);
1534 UISprite? icon = GUIStyle.IconOverflowIndicator;
1537 const int iconPadding = 4;
1538 icon.Draw(spriteBatch,
new Rectangle((
int)drawPos.X - iconPadding, (
int)drawPos.Y - iconPadding, (
int)maxWidth + iconPadding * 2, (
int)maxWidth + iconPadding * 2), Color.White, SpriteEffects.None);
1540 GUI.DrawString(spriteBatch, drawPos, text, GUIStyle.TextColorNormal, font: GUIStyle.SubHeadingFont);
1545 float halfSize = spriteSize / 2f;
1546 if (i > 0) { offset += halfSize; }
1547 Vector2 pos =
new Vector2(adjustedCenterX + offset, centerY);
1548 sprite.Draw(spriteBatch, pos, info.Color * 0.8f, scale: scale, origin: sprite.size / 2f);
1549 offset += halfSize + padding;
1556 foreach (var linkedEntity
in hull.linkedTo)
1558 if (linkedEntity is
Hull linkedHull)
1560 if (linkedHulls.Contains(linkedHull) || linkedHull.IsHidden) {
continue; }
1561 linkedHulls.Add(linkedHull);
1562 GetLinkedHulls(linkedHull, linkedHulls);
1569 return CreateMiniMap(sub, parent, settings,
null, out _);
1574 if (settings.Equals(
default(MiniMapSettings)))
1576 throw new ArgumentException($
"Provided {nameof(MiniMapSettings)} is not valid, did you mean {nameof(MiniMapSettings)}.{nameof(MiniMapSettings.Default)}?", nameof(settings));
1579 Dictionary<MapEntity, MiniMapGUIComponent> pointsOfInterestCollection =
new Dictionary<MapEntity, MiniMapGUIComponent>();
1585 float aspectRatio = worldBorders.Width / worldBorders.Height;
1586 float parentAspectRatio = parent.
Rect.Width / (float)parent.
Rect.Height;
1588 const float elementPadding = 0.9f;
1590 Vector2 containerScale = parentAspectRatio > aspectRatio ?
new Vector2(aspectRatio / parentAspectRatio, 1.0f) :
new Vector2(1.0f, parentAspectRatio / aspectRatio);
1594 ImmutableHashSet<Submarine> connectedSubs = sub.
GetConnectedSubs().Where(s => s.TeamID == sub.
TeamID).ToImmutableHashSet();
1595 ImmutableArray<Hull> hullList = ImmutableArray<Hull>.Empty;
1596 ImmutableDictionary<Hull, ImmutableArray<Hull>> combinedHulls = ImmutableDictionary<Hull, ImmutableArray<Hull>>.Empty;
1598 if (settings.CreateHullElements)
1600 hullList =
Hull.
HullList.Where(IsPartofSub).ToImmutableArray();
1601 combinedHulls = CombinedHulls(hullList);
1605 foreach (
Hull hull
in hullList.Where(IsStandaloneHull))
1607 RelativeEntityRect relativeRect =
new RelativeEntityRect(worldBorders, hull.
WorldRect);
1609 GUIFrame hullFrame =
new GUIFrame(
new RectTransform(relativeRect.RelativeSize, hullContainer.RectTransform) { RelativeOffset = relativeRect.RelativePosition }, style:
"ScanLines", color: settings.ElementColor)
1611 OutlineColor = settings.ElementColor,
1612 OutlineThickness = 2,
1616 pointsOfInterestCollection.Add(hull,
new MiniMapGUIComponent(hullFrame));
1620 foreach (var (mainHull, linkedHulls) in combinedHulls)
1622 MiniMapHullData data = ConstructHullPolygon(mainHull, linkedHulls, hullContainer, worldBorders);
1624 RelativeEntityRect relativeRect =
new RelativeEntityRect(worldBorders, data.Bounds);
1626 float highestY = 0f,
1629 foreach (var (r, _) in data.RectDatas)
1631 float y = r.Y - -r.Height,
1634 if (y > highestY) { highestY = y; }
1635 if (x > highestX) { highestX = x; }
1638 Dictionary<Hull, GUIFrame> hullsAndFrames =
new Dictionary<Hull, GUIFrame>();
1640 foreach (var (snappredRect, hull) in data.RectDatas)
1642 RectangleF rect = snappredRect;
1643 rect.Height = -rect.Height;
1644 rect.Y -= rect.Height;
1646 var (parentW, parentH) = hullContainer.Rect.Size.ToVector2();
1647 Vector2 size =
new Vector2(rect.Width / parentW, rect.Height / parentH);
1648 Vector2 pos =
new Vector2(rect.X / parentW, rect.Y / parentH);
1650 GUIFrame hullFrame =
new GUIFrame(
new RectTransform(size, hullContainer.RectTransform) { RelativeOffset = pos }, style:
"ScanLinesSeamless", color: settings.ElementColor)
1653 UVOffset =
new Vector2(highestX - rect.X, highestY - rect.Y)
1656 hullsAndFrames.Add(hull, hullFrame);
1664 foreach (var (hull1, frame1) in hullsAndFrames)
1667 foreach (var (hull2, frame2) in hullsAndFrames)
1669 if (hull2 == hull1) {
continue; }
1672 Point size = frame1.RectTransform.NonScaledSize;
1674 const int treshold = 2;
1676 int diffY = rect2.Top - rect1.Bottom;
1677 int diffX = rect2.Left - rect1.Right;
1679 if (diffY <= treshold && diffY > 0)
1684 if (diffX <= treshold && diffX > 0)
1689 frame1.RectTransform.NonScaledSize = size;
1695 foreach (List<Vector2> list in data.Polygon)
1697 spriteBatch.DrawPolygonInner(hullContainer.Rect.Location.ToVector2(), list, component.OutlineColor, 2f);
1699 }, (deltaTime, component) =>
1701 if (component.Parent.Rect.Size != data.ParentSize)
1703 data = ConstructHullPolygon(mainHull, linkedHulls, hullContainer, worldBorders);
1707 UserData = hullsAndFrames.Values.ToHashSet(),
1708 OutlineColor = settings.ElementColor,
1709 CanBeFocused =
false
1712 foreach (var (hull, component) in hullsAndFrames)
1714 pointsOfInterestCollection.Add(hull,
new MiniMapGUIComponent(component, linkedHullFrame));
1718 if (pointsOfInterest !=
null)
1720 foreach (
MapEntity entity
in pointsOfInterest)
1722 RelativeEntityRect relativeRect =
new RelativeEntityRect(worldBorders, entity.
WorldRect);
1724 GUIFrame poiComponent =
new GUIFrame(
new RectTransform(relativeRect.RelativeSize, hullContainer.RectTransform) { RelativeOffset = relativeRect.RelativePosition }, style:
null)
1726 CanBeFocused =
false,
1730 pointsOfInterestCollection.Add(entity,
new MiniMapGUIComponent(poiComponent));
1734 elements = pointsOfInterestCollection.ToImmutableDictionary();
1736 return hullContainer;
1738 bool IsPartofSub(MapEntity entity)
1740 if (entity.Submarine != sub && !connectedSubs.Contains(entity.Submarine) || entity.IsHidden) {
return false; }
1741 return sub.IsEntityFoundOnThisSub(entity,
true);
1744 bool IsStandaloneHull(Hull hull)
1746 return !combinedHulls.ContainsKey(hull) && !combinedHulls.Values.Any(hh => hh.Contains(hull));
1750 private static ImmutableDictionary<Hull, ImmutableArray<Hull>> CombinedHulls(ImmutableArray<Hull> hulls)
1752 Dictionary<Hull, HashSet<Hull>> combinedHulls =
new Dictionary<Hull, HashSet<Hull>>();
1754 foreach (Hull hull
in hulls)
1756 if (combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull))) {
continue; }
1758 List<Hull> linkedHulls =
new List<Hull>();
1759 GetLinkedHulls(hull, linkedHulls);
1761 linkedHulls.Remove(hull);
1763 foreach (Hull linkedHull
in linkedHulls)
1765 if (!combinedHulls.ContainsKey(hull))
1767 combinedHulls.Add(hull,
new HashSet<Hull>());
1770 combinedHulls[hull].Add(linkedHull);
1774 return combinedHulls.ToImmutableDictionary(pair => pair.Key, pair => pair.Value.ToImmutableArray());
1777 private static MiniMapHullData ConstructHullPolygon(Hull mainHull, ImmutableArray<Hull> linkedHulls, GUIComponent parent, RectangleF worldBorders)
1781 Dictionary<Hull, Rectangle> rects =
new Dictionary<Hull, Rectangle>();
1782 Rectangle worldRect = mainHull.WorldRect;
1783 worldRect.Y = -worldRect.Y;
1785 rects.Add(mainHull, worldRect);
1787 foreach (Hull hull
in linkedHulls)
1792 worldRect =
Rectangle.Union(worldRect, rect);
1793 rects.Add(hull, rect);
1796 worldRect.Y = -worldRect.Y;
1798 List<RectangleF> normalizedRects =
new List<RectangleF>();
1799 List<Hull> hullRefs =
new List<Hull>();
1801 foreach (var (hull, rect) in rects)
1806 var (posX, posY, sizeX, sizeY) =
new RelativeEntityRect(worldBorders, wRect);
1808 RectangleF newRect =
new RectangleF(posX * parentRect.Width, posY * parentRect.Height, sizeX * parentRect.Width, sizeY * parentRect.Height);
1810 normalizedRects.Add(newRect);
1816 ImmutableArray<RectangleF> snappedRectangles = ToolBox.SnapRectangles(normalizedRects, treshold: 1);
1818 List<List<Vector2>> polygon = ToolBox.CombineRectanglesIntoShape(snappedRectangles);
1820 List<List<Vector2>> scaledPolygon =
new List<List<Vector2>>();
1822 foreach (List<Vector2> list
in polygon)
1825 var (polySizeX, polySizeY) = ToolBox.GetPolygonBoundingBoxSize(list);
1826 float sizeX = polySizeX - 1f,
1827 sizeY = polySizeY - 1f;
1829 scaledPolygon.Add(ToolBox.ScalePolygon(list,
new Vector2(sizeX / polySizeX, sizeY / polySizeY)));
1832 return new MiniMapHullData(scaledPolygon, worldRect, parentRect.Size, snappedRectangles, hullRefs.ToImmutableArray());
1838 if (source ==
null || source.
CurrentHull ==
null) {
return; }
1841 if (!hullDatas.TryGetValue(sourceHull, out HullData? hullData))
1843 hullData =
new HullData();
1844 hullDatas.Add(sourceHull, hullData);
1847 if (hullData.Distort) {
return; }
1849 switch (connection.
Name)
1851 case "water_data_in":
1853 bool fromWaterDetector = source.GetComponent<
WaterDetector>() !=
null;
1854 hullData.ReceivedWaterAmount =
null;
1855 hullData.LastWaterDataTime = Timing.TotalTime;
1856 if (fromWaterDetector)
1860 foreach (var linked
in sourceHull.
linkedTo)
1862 if (linked is not
Hull linkedHull) {
continue; }
1863 if (!hullDatas.TryGetValue(linkedHull, out HullData? linkedHullData))
1865 linkedHullData =
new HullData();
1866 hullDatas.Add(linkedHull, linkedHullData);
1868 linkedHullData.ReceivedWaterAmount =
null;
1869 if (fromWaterDetector)
1875 case "oxygen_data_in":
1876 if (!
float.TryParse(signal.
value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out
float oxy))
1878 oxy = Rand.Range(0.0f, 100.0f);
1880 hullData.ReceivedOxygenAmount = oxy;
1881 hullData.LastOxygenDataTime = Timing.TotalTime;
1882 foreach (var linked
in sourceHull.
linkedTo)
1884 if (linked is not
Hull linkedHull) {
continue; }
1885 if (!hullDatas.TryGetValue(linkedHull, out HullData? linkedHullData))
1887 linkedHullData =
new HullData();
1888 hullDatas.Add(linkedHull, linkedHullData);
1890 linkedHullData.ReceivedOxygenAmount = oxy;
1898 base.RemoveComponentSpecific();
1899 if (searchAutoComplete !=
null)
1901 searchAutoComplete.RectTransform.Parent =
null;
1902 searchAutoComplete =
null;
1904 if (hullInfoFrame !=
null)
1906 hullInfoFrame.RectTransform.Parent =
null;
1907 hullInfoFrame =
null;
Responsible for keeping track of the characters in the player crew, saving and loading their orders,...
static void CreateReportButtons(CrewManager crewManager, GUIComponent parent, IReadOnlyList< OrderPrefab > reports, bool isHorizontal)
virtual void ClearChildren()
RectTransform RectTransform
IEnumerable< GUIComponent > Children
GUIComponent that can be used to render custom content on the UI
static GameSession?? GameSession
static int GraphicsHeight
static readonly List< Hull > HullList
static readonly List< Item > ItemList
static readonly PrefabCollection< ItemPrefab > Prefabs
override LocalizedString Name
virtual bool Select(Character character)
void TryCreateDragHandle()
static readonly Color MiniMapBaseColor
override void CreateGUI()
Overload this method and implement. The method is automatically called when the resolution changes.
override void RemoveComponentSpecific()
static GUIFrame CreateMiniMap(Submarine sub, GUIComponent parent, MiniMapSettings settings)
override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
override void ReceiveSignal(Signal signal, Connection connection)
bool EnableElectricalView
override void AddToGUIUpdateList(int order=0)
static void GetLinkedHulls(Hull hull, List< Hull > linkedHulls)
static GUIFrame CreateMiniMap(Submarine sub, GUIComponent parent, MiniMapSettings settings, IEnumerable< MapEntity >? pointsOfInterest, out ImmutableDictionary< MapEntity, MiniMapGUIComponent > elements)
static int GetWaterPercentage(Hull hull)
readonly List< MapEntity > linkedTo
static readonly PrefabCollection< OrderPrefab > Prefabs
List< Item > GetItems(bool alsoFromConnectedSubs)
override Vector2? WorldPosition
IEnumerable< Submarine > DockedTo
IEnumerable< Submarine > GetConnectedSubs()
Returns a list of all submarines that are connected to this one via docking ports,...
Rectangle GetDockedBorders(bool allowDifferentTeam=true)
Returns a rect that contains the borders of this sub and all subs docked to it, excluding outposts
bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs, bool allowDifferentTeam=false, bool allowDifferentType=false)