4 using Microsoft.Xna.Framework;
5 using Microsoft.Xna.Framework.Graphics;
7 using System.Collections.Generic;
26 private readonly
bool dynamicDockingIndicator =
true;
28 private bool unsentChanges;
29 private float networkUpdateTimer;
32 private GUITickBox activeTickBox, passiveTickBox;
40 private Vector2? pingDragDirection =
null;
51 private Sprite directionalPingBackground;
52 private Sprite[] directionalPingButton;
54 private Sprite pingCircle, directionalPingCircle;
55 private Sprite screenOverlay, screenBackground;
60 private readonly Dictionary<Identifier, Tuple<Sprite, Color>> targetIcons =
new Dictionary<Identifier, Tuple<Sprite, Color>>();
62 private float displayBorderSize;
64 private List<SonarBlip> sonarBlips;
66 private float prevPassivePingRadius;
68 private Vector2 center;
79 private const float DisruptionUpdateInterval = 0.2f;
80 private float disruptionUpdateTimer;
82 private const float LongRangeUpdateInterval = 10.0f;
83 private float longRangeUpdateTimer;
85 private float showDirectionalIndicatorTimer;
87 private readonly List<LevelObject> nearbyObjects =
new List<LevelObject>();
88 private const float NearbyObjectUpdateInterval = 1.0f;
89 float nearbyObjectUpdateTimer;
91 private readonly List<Submarine> connectedSubs =
new List<Submarine>();
92 private const float ConnectedSubUpdateInterval = 1.0f;
93 float connectedSubUpdateTimer;
95 private readonly List<(Vector2 pos,
float strength)> disruptedDirections =
new List<(Vector2 pos,
float strength)>();
97 private readonly Dictionary<object, CachedDistance> markerDistances =
new Dictionary<object, CachedDistance>();
99 private readonly Color positiveColor = Color.Green;
100 private readonly Color warningColor = Color.Orange;
101 private readonly Color negativeColor = Color.Red;
102 private readonly Color markerColor = Color.Red;
106 private static readonly
float sonarAreaSize = 1.09f;
108 private static readonly Dictionary<BlipType, Color[]> blipColorGradient =
new Dictionary<BlipType, Color[]>()
112 new Color[] { Color.TransparentBlack,
new Color(0, 50, 160),
new Color(0, 133, 166),
new Color(2, 159, 30),
new Color(255, 255, 255) }
116 new Color[] { Color.TransparentBlack,
new Color(254, 68, 19),
new Color(255, 220, 62),
new Color(255, 255, 255) }
120 new Color[] { Color.TransparentBlack,
new Color(94, 114, 73) * 0.8f,
new Color(255, 236, 151) * 0.8f,
new Color(242, 243, 194) * 0.8f }
124 new Color[] { Color.TransparentBlack,
new Color(73, 78, 86),
new Color(66, 94, 100),
new Color(47, 115, 58),
new Color(255, 255, 255) }
128 new Color[] { Color.TransparentBlack, Color.TransparentBlack,
new Color(254, 68, 19) * 0.8f, Color.TransparentBlack }
132 private float prevDockingDist;
138 public static Vector2
GUISizeCalculation => Vector2.One * Math.Min(GUI.RelativeHorizontalAspectRatio, 1f) * sonarAreaSize;
140 private List<(Vector2 center, List<Item> resources)> MineralClusters {
get;
set; }
142 private readonly List<GUITextBlock> textBlocksToScaleAndNormalize =
new List<GUITextBlock>();
144 private bool isConnectedToSteering;
160 System.Diagnostics.Debug.Assert(Enum.GetValues(typeof(
BlipType)).Cast<BlipType>().
All(t => blipColorGradient.ContainsKey(t)));
161 sonarBlips =
new List<SonarBlip>();
165 TextManager.Get(
"missiontype.nest"));
167 foreach (var subElement
in element.Elements())
169 switch (subElement.Name.ToString().ToLowerInvariant())
172 pingCircle =
new Sprite(subElement);
174 case "directionalpingcircle":
175 directionalPingCircle =
new Sprite(subElement);
177 case "directionalpingbackground":
178 directionalPingBackground =
new Sprite(subElement);
180 case "directionalpingbutton":
181 if (directionalPingButton ==
null) { directionalPingButton =
new Sprite[3]; }
182 int index = subElement.GetAttributeInt(
"index", 0);
183 directionalPingButton[index] =
new Sprite(subElement);
185 case "screenoverlay":
186 screenOverlay =
new Sprite(subElement);
188 case "screenbackground":
189 screenBackground =
new Sprite(subElement);
192 sonarBlip =
new Sprite(subElement);
195 lineSprite =
new Sprite(subElement);
198 var targetIconSprite =
new Sprite(subElement);
199 var color = subElement.GetAttributeColor(
"color", Color.White);
200 targetIcons.Add(subElement.GetAttributeIdentifier(
"identifier", Identifier.Empty),
201 new Tuple<Sprite, Color>(targetIconSprite, color));
215 isConnectedToSteering =
item.GetComponent<
Steering>() !=
null;
216 Vector2 size = isConnectedToSteering ?
controlBoxSize :
new Vector2(0.46f, 0.4f);
219 if (!isConnectedToSteering && !GUI.IsFourByThree())
221 controlContainer.
RectTransform.
MaxSize =
new Point((
int)(380 * GUI.xScale), (
int)(300 * GUI.yScale));
225 AbsoluteOffset = GUIStyle.ItemFrameOffset
228 float extraHeight = 0.0694f;
229 var sonarModeArea =
new GUIFrame(
new RectTransform(
new Vector2(1, 0.4f + extraHeight), paddedControlContainer.RectTransform,
Anchor.TopCenter), style:
null);
236 OnClicked = (button, data) =>
238 button.Selected = !button.Selected;
242 unsentChanges =
true;
250 RelativeOffset = new Vector2(SonarModeSwitch.RectTransform.RelativeSize.X, 0)
253 TextManager.Get(
"SonarPassive"), font: GUIStyle.SubHeadingFont, style:
"IndicatorLightRedSmall")
256 ToolTip = TextManager.Get(
"SonarTipPassive"),
261 TextManager.Get(
"SonarActive"), font: GUIStyle.SubHeadingFont, style:
"IndicatorLightRedSmall")
264 ToolTip = TextManager.Get(
"SonarTipActive"),
271 textBlocksToScaleAndNormalize.Clear();
272 textBlocksToScaleAndNormalize.Add(passiveTickBox.
TextBlock);
273 textBlocksToScaleAndNormalize.Add(activeTickBox.
TextBlock);
275 lowerAreaFrame =
new GUIFrame(
new RectTransform(
new Vector2(1, 0.4f + extraHeight), paddedControlContainer.RectTransform,
Anchor.BottomCenter), style:
null);
278 TextManager.Get(
"SonarZoom"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight);
279 textBlocksToScaleAndNormalize.Add(zoomText);
282 RelativeOffset = new Vector2(0.35f, 0)
283 }, barSize: 0.15f, isHorizontal:
true, style:
"DeviceSlider")
285 OnMoved = (scrollbar, scroll) =>
287 zoom = MathHelper.Lerp(MinZoom, MaxZoom, scroll);
290 unsentChanges =
true;
297 new GUIFrame(
new RectTransform(
new Vector2(0.8f, 0.01f), paddedControlContainer.RectTransform,
Anchor.Center), style:
"HorizontalLine")
299 UserData =
"horizontalline"
306 directionalModeSwitch =
new GUIButton(
new RectTransform(
new Vector2(0.3f, 0.8f), directionalModeFrame.RectTransform,
Anchor.CenterLeft),
string.Empty, style:
"SwitchHorizontal")
308 OnClicked = (button, data) =>
310 useDirectionalPing = !useDirectionalPing;
311 button.Selected = useDirectionalPing;
314 unsentChanges =
true;
321 TextManager.Get(
"SonarDirectionalPing"), GUIStyle.TextColorNormal, GUIStyle.SubHeadingFont, Alignment.CenterLeft);
322 textBlocksToScaleAndNormalize.Add(directionalModeSwitchText);
326 AddMineralScannerSwitchToGUI();
330 mineralScannerSwitch =
null;
338 (spriteBatch, guiCustomComponent) => { DrawSonar(spriteBatch, guiCustomComponent.Rect); },
null);
341 "", warningColor, GUIStyle.LargeFont, Alignment.Center);
355 else if (GUI.RelativeHorizontalAspectRatio > 0.75f)
365 handle.RectTransform.SetAsFirstChild();
369 private void SetPingDirection(Vector2 direction)
371 pingDirection = direction;
374 unsentChanges =
true;
379 private Vector2 GetTransducerPos()
387 Vector2 transducerPosSum = Vector2.Zero;
388 foreach (ConnectedTransducer transducer
in connectedTransducers)
392 return transducer.Transducer.Item.Submarine.WorldPosition;
394 transducerPosSum += transducer.Transducer.Item.WorldPosition;
396 return transducerPosSum / connectedTransducers.Count;
403 zoomSlider.
BarScroll = MathUtils.InverseLerp(MinZoom, MaxZoom, zoom);
406 AddMineralScannerSwitchToGUI();
410 item.GetComponent<
Steering>()?.AttachToSonarHUD(sonarView);
413 private void AddMineralScannerSwitchToGUI()
423 new Vector2(0.0f, -0.1f);
437 mineralScannerSwitch =
new GUIButton(
new RectTransform(
new Vector2(0.3f, 0.8f), mineralScannerFrame.RectTransform,
Anchor.CenterLeft),
string.Empty, style:
"SwitchHorizontal")
440 OnClicked = (button, data) =>
446 unsentChanges =
true;
453 var mineralScannerSwitchText =
new GUITextBlock(
new RectTransform(
new Vector2(0.7f, 1), mineralScannerFrame.RectTransform,
Anchor.CenterRight),
454 TextManager.Get(
"SonarMineralScanner"), GUIStyle.TextColorNormal, GUIStyle.SubHeadingFont, Alignment.CenterLeft);
455 textBlocksToScaleAndNormalize.Add(mineralScannerSwitchText);
457 PreventMineralScannerOverlap();
460 private void PreventMineralScannerOverlap()
462 if (
item.GetComponent<Steering>() is { } steering && controlContainer is { } container)
464 int containerBottom = container.Rect.Y + container.Rect.Height,
465 steeringTop = steering.ControlContainer.Rect.Top;
467 int amountRaised = 0;
469 while (GetContainerBottom() > steeringTop) { amountRaised++; }
471 container.RectTransform.AbsoluteOffset =
new Point(0, -amountRaised);
473 int GetContainerBottom() => containerBottom - amountRaised;
479 showDirectionalIndicatorTimer -= deltaTime;
484 if (networkUpdateTimer <= 0.0f)
486 item.CreateClientEvent(
this);
488 networkUpdateTimer = 0.1f;
489 unsentChanges =
false;
492 networkUpdateTimer -= deltaTime;
495 connectedSubUpdateTimer -= deltaTime;
496 if (connectedSubUpdateTimer <= 0.0f)
498 connectedSubs.Clear();
501 foreach (var transducer
in connectedTransducers)
503 if (transducer.Transducer.Item.Submarine ==
null) {
continue; }
504 if (connectedSubs.Contains(transducer.Transducer.Item.Submarine)) {
continue; }
505 connectedSubs.AddRange(transducer.Transducer.Item.Submarine.GetConnectedSubs());
512 connectedSubUpdateTimer = ConnectedSubUpdateInterval;
517 (GUI.MouseOn ==
null || GUI.MouseOn == sonarView || sonarView.
IsParentOf(GUI.MouseOn) || GUI.MouseOn == steering?.
GuiFrame || (steering?.
GuiFrame?.
IsParentOf(GUI.MouseOn) ??
false)))
520 if (Math.Abs(scrollSpeed) > 0.0001f)
527 Vector2 transducerCenter = GetTransducerPos();
541 for (
int i = sonarBlips.Count - 1; i >= 0; i--)
543 sonarBlips[i].FadeTimer -= deltaTime * MathHelper.Lerp(0.5f, 2.0f, distort);
544 sonarBlips[i].Position += sonarBlips[i].Velocity * deltaTime;
546 if (sonarBlips[i].FadeTimer <= 0.0f) { sonarBlips.RemoveAt(i); }
552 (sonarView.
Rect.Width / 2 * sonarView.
Rect.Width / 2);
556 if (MineralClusters ==
null)
558 MineralClusters =
new List<(Vector2, List<Item>)>();
564 if (c.Resources ==
null) {
return; }
565 if (c.Resources.None(i => i !=
null && !i.Removed && i.Tags.Contains(
"ore"))) {
return; }
566 var pos = Vector2.Zero;
567 foreach (var r
in c.Resources)
569 pos += r.WorldPosition;
571 pos /= c.Resources.Count;
572 MineralClusters.Add((center: pos, resources: c.Resources));
577 MineralClusters.RemoveAll(c => c.resources ==
null || c.resources.None() || c.resources.All(i => i ==
null || i.Removed));
588 nearbyObjectUpdateTimer -= deltaTime;
589 if (nearbyObjectUpdateTimer <= 0.0f)
591 nearbyObjects.Clear();
594 if (!nearbyObject.VisibleOnSonar) {
continue; }
595 float objectRange = range + nearbyObject.SonarRadius;
596 if (Vector2.DistanceSquared(transducerCenter, nearbyObject.WorldPosition) < objectRange * objectRange)
598 nearbyObjects.Add(nearbyObject);
601 nearbyObjectUpdateTimer = NearbyObjectUpdateInterval;
604 List<LevelTrigger> ballastFloraSpores =
new List<LevelTrigger>();
605 Dictionary<LevelTrigger, Vector2> levelTriggerFlows =
new Dictionary<LevelTrigger, Vector2>();
606 for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex)
608 var activePing = activePings[pingIndex];
609 float pingRange = range * activePing.State / zoom;
612 if (levelObject.
Triggers ==
null) {
continue; }
618 if (flow.LengthSquared() >= 1.0f && !levelTriggerFlows.ContainsKey(trigger))
620 levelTriggerFlows.Add(trigger, flow);
623 Vector2.DistanceSquared(transducerCenter, trigger.
WorldPosition) < pingRange / 2 * pingRange / 2)
625 ballastFloraSpores.Add(trigger);
631 foreach (KeyValuePair<LevelTrigger, Vector2> triggerFlow
in levelTriggerFlows)
634 Vector2 flow = triggerFlow.Value;
636 float flowMagnitude = flow.Length();
637 if (Rand.Range(0.0f, 1.0f) < flowMagnitude / 1000.0f)
639 float edgeDist = Rand.Range(0.0f, 1.0f);
641 Vector2 blipVel = flow;
644 foreach (KeyValuePair<LevelTrigger, Vector2> triggerFlow2
in levelTriggerFlows)
649 Vector2 trigger2flow = triggerFlow2.Value;
651 blipVel += trigger2flow;
654 var flowBlip =
new SonarBlip(blipPos, Rand.Range(0.5f, 1.0f), 1.0f)
656 Velocity = blipVel * Rand.Range(1.0f, 5.0f),
657 Size =
new Vector2(MathHelper.Lerp(0.4f, 5f, flowMagnitude / 500.0f), 0.2f),
658 Rotation = (float)Math.Atan2(-blipVel.Y, blipVel.X)
660 sonarBlips.Add(flowBlip);
669 Rotation = Rand.Range(-MathHelper.TwoPi, MathHelper.TwoPi),
671 Velocity = Rand.Vector(100f, Rand.RandSync.Unsynced)
674 sonarBlips.Add(sporeBlip);
677 float outsideLevelFlow = 0.0f;
678 if (transducerCenter.X < 0.0f)
680 outsideLevelFlow = Math.Abs(transducerCenter.X * 0.001f);
684 outsideLevelFlow = -(transducerCenter.X -
Level.
Loaded.
Size.X) * 0.001f;
687 if (Rand.Range(0.0f, 100.0f) < Math.Abs(outsideLevelFlow))
689 Vector2 blipPos = transducerCenter + Rand.Vector(Rand.Range(0.0f, range));
690 var flowBlip =
new SonarBlip(blipPos, Rand.Range(0.5f, 1.0f), 1.0f)
692 Velocity = Vector2.UnitX * outsideLevelFlow * Rand.Range(50.0f, 100.0f),
693 Size =
new Vector2(Rand.Range(0.4f, 5f), 0.2f),
696 sonarBlips.Add(flowBlip);
706 zoom = Math.Max(zoom, MathHelper.Lerp(MinZoom, MaxZoom, zoomSlider.
BarScroll));
711 zoom = Math.Max(zoom, MathHelper.Lerp(MinZoom, MaxZoom, zoomSlider.
BarScroll));
716 zoom = Math.Max(zoom, MathHelper.Lerp(MinZoom, MaxZoom, zoomSlider.
BarScroll));
718 prevDockingDist = Math.Min(dockingDist, prevDockingDist);
722 prevDockingDist =
float.MaxValue;
725 if (steering !=
null && directionalPingButton !=
null)
727 steering.
SteerRadius = useDirectionalPing && pingDragDirection !=
null ?
730 (
float?)((sonarView.
Rect.Width / 2) - (directionalPingButton[0].
size.X * sonarView.
Rect.Width / screenBackground.
size.X)) :
734 if (useDirectionalPing)
739 pingDragDirection = newDragDir;
744 float newAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(newDragDir));
745 SetPingDirection(
new Vector2((
float)Math.Cos(newAngle), (
float)Math.Sin(newAngle)));
749 pingDragDirection =
null;
754 pingDragDirection =
null;
757 disruptionUpdateTimer -= deltaTime;
758 for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex)
760 var activePing = activePings[pingIndex];
762 if (disruptionUpdateTimer <= 0.0f) { UpdateDisruptions(transducerCenter, pingRadius /
DisplayScale); }
763 Ping(transducerCenter, transducerCenter,
764 pingRadius, activePing.PrevPingRadius,
DisplayScale, range / zoom, passive:
false, pingStrength: 2.0f);
765 activePing.PrevPingRadius = pingRadius;
767 if (disruptionUpdateTimer <= 0.0f)
769 disruptionUpdateTimer = DisruptionUpdateInterval;
772 longRangeUpdateTimer -= deltaTime;
773 if (longRangeUpdateTimer <= 0.0f)
785 float dist = targetVector.Length();
786 Vector2 targetDir = targetVector / dist;
787 int blipCount = (int)MathHelper.Clamp(c.
Mass, 50, 200);
788 for (
int i = 0; i < blipCount; i++)
790 float angle = Rand.Range(-0.5f, 0.5f);
791 Vector2 blipDir = MathUtils.RotatePoint(targetDir, angle);
792 Vector2 invBlipDir = MathUtils.RotatePoint(targetDir, -angle);
793 var longRangeBlip =
new SonarBlip(transducerCenter + blipDir *
Range * 0.9f, Rand.Range(1.9f, 2.1f), Rand.Range(1.0f, 1.5f),
BlipType.LongRange)
795 Velocity = -invBlipDir * (MathUtils.Round(Rand.Range(8000.0f, 15000.0f), 2000.0f) - Math.Abs(angle * angle * 10000.0f)),
796 Rotation = (
float)Math.Atan2(-invBlipDir.Y, invBlipDir.X),
799 longRangeBlip.
Size.Y *= 5.0f;
800 sonarBlips.Add(longRangeBlip);
804 longRangeUpdateTimer = LongRangeUpdateInterval;
807 if (currentMode ==
Mode.Active && currentPingIndex != -1)
812 float passivePingRadius = (float)(Timing.TotalTime % 1.0f);
813 if (passivePingRadius > 0.0f)
815 if (activePingsCount == 0) { disruptedDirections.Clear(); }
822 float distSqr = Vector2.DistanceSquared(t.
WorldPosition, transducerCenter);
825 float dist = (float)Math.Sqrt(distSqr);
826 if (dist > prevPassivePingRadius *
Range && dist <= passivePingRadius *
Range && Rand.Int(sonarBlips.Count) < 500)
830 passive:
true, pingStrength: 0.5f, needsToBeInSector: t);
831 if (t.IsWithinSector(transducerCenter))
833 sonarBlips.Add(
new SonarBlip(t.WorldPosition, fadeTimer: 1.0f, scale: MathHelper.Clamp(t.SoundRange / 2000, 1.0f, 5.0f)));
838 prevPassivePingRadius = passivePingRadius;
841 private bool MouseInDirectionalPingRing(Rectangle rect,
bool onButton)
843 if (!useDirectionalPing || directionalPingButton ==
null) {
return false; }
845 float endRadius = rect.Width / 2.0f;
846 float startRadius = endRadius - directionalPingButton[0].
size.X * rect.Width / screenBackground.
size.X;
848 Vector2 center = rect.Center.ToVector2();
852 bool retVal = (dist >= startRadius*startRadius) && (dist < endRadius*endRadius);
855 float pingAngle = MathUtils.VectorToAngle(pingDirection);
857 retVal &= Math.Abs(MathUtils.GetShortestAngle(mouseAngle, pingAngle)) < MathHelper.ToRadians(DirectionalPingSector * 0.5f);
863 private void DrawSonar(SpriteBatch spriteBatch, Rectangle rect)
865 displayBorderSize = 0.2f;
866 center = rect.Center.ToVector2();
867 DisplayRadius = (rect.Width / 2.0f) * (1.0f - displayBorderSize);
870 screenBackground?.
Draw(spriteBatch, center, 0.0f, rect.Width / screenBackground.
size.X);
872 if (useDirectionalPing)
874 directionalPingBackground?.
Draw(spriteBatch, center, 0.0f, rect.Width / directionalPingBackground.
size.X);
875 if (directionalPingButton !=
null)
877 int buttonSprIndex = 0;
878 if (pingDragDirection !=
null)
882 else if (MouseInDirectionalPingRing(rect,
true))
886 directionalPingButton[buttonSprIndex]?.
Draw(spriteBatch, center, MathUtils.VectorToAngle(pingDirection), rect.Width / directionalPingBackground.
size.X);
890 if (currentPingIndex != -1)
892 var activePing = activePings[currentPingIndex];
893 if (activePing.IsDirectional && directionalPingCircle !=
null)
895 directionalPingCircle.
Draw(spriteBatch, center, Color.White * (1.0f - activePing.State),
896 rotate: MathUtils.VectorToAngle(activePing.Direction),
901 pingCircle.
Draw(spriteBatch, center, Color.White * (1.0f - activePing.State), 0.0f, (
DisplayRadius * 2 / pingCircle.
size.X) * activePing.State);
905 float signalStrength = 1.0f;
908 signalStrength = 0.0f;
909 foreach (ConnectedTransducer connectedTransducer
in connectedTransducers)
911 signalStrength = Math.Max(signalStrength, connectedTransducer.SignalStrength);
915 Vector2 transducerCenter = GetTransducerPos();
917 if (sonarBlips.Count > 0)
919 float blipScale = 0.08f * (float)Math.Sqrt(zoom) * (rect.Width / 700.0f);
921 spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
923 foreach (SonarBlip sonarBlip
in sonarBlips)
925 DrawBlip(spriteBatch, sonarBlip, transducerCenter +
DisplayOffset, center, sonarBlip.FadeTimer / 2.0f * signalStrength, blipScale);
929 spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied);
935 DrawDockingPorts(spriteBatch, transducerCenter, signalStrength);
936 DrawOwnSubmarineBorders(spriteBatch, transducerCenter, signalStrength);
943 float directionalPingVisibility = useDirectionalPing && currentMode ==
Mode.Active ? 1.0f : showDirectionalIndicatorTimer;
944 if (directionalPingVisibility > 0.0f)
946 Vector2 sector1 = MathUtils.RotatePointAroundTarget(pingDirection *
DisplayRadius, Vector2.Zero, MathHelper.ToRadians(DirectionalPingSector * 0.5f));
947 Vector2 sector2 = MathUtils.RotatePointAroundTarget(pingDirection *
DisplayRadius, Vector2.Zero, MathHelper.ToRadians(-DirectionalPingSector * 0.5f));
948 DrawLine(spriteBatch, Vector2.Zero, sector1, Color.LightCyan * 0.2f * directionalPingVisibility, width: 3);
949 DrawLine(spriteBatch, Vector2.Zero, sector2, Color.LightCyan * 0.2f * directionalPingVisibility, width: 3);
952 if (GameMain.DebugDraw)
954 GUI.DrawString(spriteBatch, rect.Location.ToVector2(), sonarBlips.Count.ToString(), Color.White);
957 screenOverlay?.
Draw(spriteBatch, center, 0.0f, rect.Width / screenOverlay.
size.X);
959 if (signalStrength <= 0.5f)
961 signalWarningText.
Text = TextManager.Get(signalStrength <= 0.0f ?
"SonarNoSignal" :
"SonarSignalWeak");
962 signalWarningText.
Color = signalStrength <= 0.0f ? negativeColor : warningColor;
963 signalWarningText.
Visible =
true;
968 signalWarningText.
Visible =
false;
971 foreach (AITarget aiTarget
in AITarget.List)
973 if (aiTarget.InDetectable) {
continue; }
974 if (aiTarget.SonarLabel.IsNullOrEmpty() || aiTarget.SoundRange <= 0.0f) {
continue; }
976 if (Vector2.DistanceSquared(aiTarget.WorldPosition, transducerCenter) < aiTarget.SoundRange * aiTarget.SoundRange)
978 DrawMarker(spriteBatch,
979 aiTarget.SonarLabel.Value,
980 aiTarget.SonarIconIdentifier,
982 aiTarget.WorldPosition, transducerCenter,
987 if (GameMain.GameSession ==
null) {
return; }
989 if (Level.Loaded !=
null)
991 if (Level.Loaded.StartLocation?.Type is { ShowSonarMarker: true })
993 DrawMarker(spriteBatch,
994 Level.Loaded.StartLocation.DisplayName.Value,
995 (Level.Loaded.StartOutpost !=
null ?
"outpost" :
"location").ToIdentifier(),
997 Level.Loaded.StartExitPosition, transducerCenter,
1001 if (Level.Loaded is { EndLocation.Type.ShowSonarMarker: true, Type: LevelData.LevelType.LocationConnection })
1003 DrawMarker(spriteBatch,
1004 Level.Loaded.EndLocation.DisplayName.Value,
1005 (Level.Loaded.EndOutpost !=
null ?
"outpost" :
"location").ToIdentifier(),
1007 Level.Loaded.EndExitPosition, transducerCenter,
1011 for (
int i = 0; i < Level.Loaded.Caves.Count; i++)
1013 var cave = Level.Loaded.Caves[i];
1014 if (cave.MissionsToDisplayOnSonar.None()) {
continue; }
1015 DrawMarker(spriteBatch,
1017 "cave".ToIdentifier(),
1019 cave.StartPos.ToVector2(), transducerCenter,
1024 int missionIndex = 0;
1025 foreach (Mission mission
in GameMain.GameSession.Missions)
1028 foreach ((LocalizedString label, Vector2 position) in mission.SonarLabels)
1030 if (!
string.IsNullOrEmpty(label.Value))
1032 DrawMarker(spriteBatch,
1034 mission.SonarIconIdentifier,
1035 "mission" + missionIndex +
":" + i,
1036 position, transducerCenter,
1047 foreach (var c
in MineralClusters)
1049 var unobtainedMinerals = c.resources.Where(i => i !=
null && i.GetComponent<Holdable>() is { Attached: true });
1050 if (unobtainedMinerals.None()) {
continue; }
1051 if (!CheckResourceMarkerVisibility(c.center, transducerCenter)) {
continue; }
1052 var i = unobtainedMinerals.FirstOrDefault();
1053 if (i ==
null) {
continue; }
1055 bool disrupted =
false;
1056 foreach ((Vector2 disruptPos,
float disruptStrength) in disruptedDirections)
1058 float dot = Vector2.Dot(Vector2.Normalize(c.center - transducerCenter), disruptPos);
1059 if (dot > 1.0f - disruptStrength)
1065 if (disrupted) {
continue; }
1067 DrawMarker(spriteBatch,
1068 i.Name,
"mineral".ToIdentifier(),
"mineralcluster" + i,
1069 c.center, transducerCenter,
1071 onlyShowTextOnMouseOver:
true);
1075 foreach (Submarine sub
in Submarine.Loaded)
1077 if (!sub.ShowSonarMarker) {
continue; }
1078 if (connectedSubs.Contains(sub)) {
continue; }
1079 if (Level.Loaded !=
null && sub.WorldPosition.Y > Level.Loaded.Size.Y) {
continue; }
1094 DrawMarker(spriteBatch,
1095 sub.Info.DisplayName.Value,
1096 (sub.Info.HasTag(
SubmarineTag.Shuttle) ?
"shuttle" :
"submarine").ToIdentifier(),
1098 sub.WorldPosition, transducerCenter,
1102 if (GameMain.DebugDraw)
1109 private void DrawOwnSubmarineBorders(SpriteBatch spriteBatch, Vector2 transducerCenter,
float signalStrength)
1111 float simScale =
DisplayScale * Physics.DisplayToSimRation;
1113 foreach (Submarine submarine
in Submarine.Loaded)
1115 if (!connectedSubs.Contains(submarine)) {
continue; }
1116 if (submarine.HullVertices ==
null) {
continue; }
1118 Vector2 offset = ConvertUnits.ToSimUnits(submarine.WorldPosition - transducerCenter);
1120 for (
int i = 0; i < submarine.HullVertices.Count; i++)
1122 Vector2 start = (submarine.HullVertices[i] + offset) * simScale;
1124 Vector2 end = (submarine.HullVertices[(i + 1) % submarine.HullVertices.Count] + offset) * simScale;
1127 DrawLine(spriteBatch, start, end, Color.LightBlue * signalStrength * 0.5f, width: 4);
1132 private void DrawLine(SpriteBatch spriteBatch, Vector2 start, Vector2 end, Color color,
int width)
1136 if (startOutside && endOutside)
1140 else if (startOutside)
1142 if (MathUtils.GetLineCircleIntersections(Vector2.Zero,
DisplayRadius, end, start,
true, out Vector2? intersection1, out _) == 1)
1144 DrawLineSprite(spriteBatch, center + intersection1.Value, center + end, color, width: width);
1147 else if (endOutside)
1149 if (MathUtils.GetLineCircleIntersections(Vector2.Zero,
DisplayRadius, start, end,
true, out Vector2? intersection1, out _) == 1)
1151 DrawLineSprite(spriteBatch, center + start, center + intersection1.Value, color, width: width);
1156 DrawLineSprite(spriteBatch, center + start, center + end, color, width: width);
1160 private void DrawLineSprite(SpriteBatch spriteBatch, Vector2 start, Vector2 end, Color color,
int width)
1162 if (lineSprite ==
null)
1164 GUI.DrawLine(spriteBatch, start, end, color, width: width);
1168 Vector2 dir = end - start;
1169 float angle = (float)Math.Atan2(dir.Y, dir.X);
1170 lineSprite.
Draw(spriteBatch, start, color, origin: lineSprite.
Origin, rotate: angle,
1171 scale:
new Vector2(dir.Length() / lineSprite.
size.X, 1.0f));
1176 private void DrawDockingPorts(SpriteBatch spriteBatch, Vector2 transducerCenter,
float signalStrength)
1181 if (steering !=
null && steering.DockingModeEnabled && steering.ActiveDockingSource !=
null)
1183 DrawDockingIndicator(spriteBatch, steering, ref transducerCenter);
1186 foreach (DockingPort dockingPort
in DockingPort.List)
1188 if (Level.Loaded !=
null && dockingPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) {
continue; }
1189 if (dockingPort.Item.IsHidden) {
continue; }
1190 if (dockingPort.Item.Submarine ==
null) {
continue; }
1191 if (dockingPort.Item.Submarine.Info.IsWreck) {
continue; }
1193 if (!dockingPort.Item.Submarine.ShowSonarMarker && dockingPort.Item.Submarine !=
item.
Submarine &&
1194 !dockingPort.Item.
Submarine.Info.IsOutpost && !dockingPort.Item.Submarine.Info.IsBeacon)
1201 item.
Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle &&
1202 dockingPort.Item.
Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle &&
1203 !dockingPort.Item.Submarine.Info.IsOutpost &&
1204 !dockingPort.Item.Submarine.Info.IsBeacon)
1210 Vector2 offset = (dockingPort.Item.WorldPosition - transducerCenter) * scale;
1211 offset.Y = -offset.Y;
1213 Vector2 size = dockingPort.Item.Rect.Size.ToVector2() * scale;
1215 if (dockingPort.IsHorizontal)
1223 GUI.DrawLine(spriteBatch, center + offset - size - Vector2.Normalize(size) * zoom, center + offset + size + Vector2.Normalize(size) * zoom, Color.Black * signalStrength * 0.5f, width: (int)(zoom * 5.0f));
1224 GUI.DrawLine(spriteBatch, center + offset - size, center + offset + size, positiveColor * signalStrength, width: (
int)(zoom * 2.5f));
1228 private void DrawDockingIndicator(SpriteBatch spriteBatch, Steering steering, ref Vector2 transducerCenter)
1232 Vector2 worldFocusPos = (steering.ActiveDockingSource.Item.WorldPosition + steering.DockingTarget.Item.WorldPosition) / 2.0f;
1233 worldFocusPos.X = steering.DockingTarget.Item.WorldPosition.X;
1235 Vector2 sourcePortDiff = (steering.ActiveDockingSource.Item.WorldPosition - transducerCenter) * scale;
1236 Vector2 sourcePortPos =
new Vector2(sourcePortDiff.X, -sourcePortDiff.Y);
1237 Vector2 targetPortDiff = (steering.DockingTarget.Item.WorldPosition - transducerCenter) * scale;
1238 Vector2 targetPortPos =
new Vector2(targetPortDiff.X, -targetPortDiff.Y);
1240 System.Diagnostics.Debug.Assert(steering.ActiveDockingSource.IsHorizontal == steering.DockingTarget.IsHorizontal);
1241 Vector2 diff = steering.DockingTarget.Item.WorldPosition - steering.ActiveDockingSource.Item.WorldPosition;
1242 float dist = diff.Length();
1244 Math.Abs(diff.X) < steering.DockingTarget.DistanceTolerance.X &&
1245 Math.Abs(diff.Y) < steering.DockingTarget.DistanceTolerance.Y;
1247 Vector2 dockingDir = sourcePortPos - targetPortPos;
1248 Vector2 normalizedDockingDir = Vector2.Normalize(dockingDir);
1249 if (!dynamicDockingIndicator)
1251 if (steering.ActiveDockingSource.IsHorizontal)
1253 normalizedDockingDir =
new Vector2(Math.Sign(normalizedDockingDir.X), 0.0f);
1257 normalizedDockingDir =
new Vector2(0.0f, Math.Sign(normalizedDockingDir.Y));
1261 Color staticLineColor = Color.White * 0.2f;
1263 float sector = MathHelper.ToRadians(MathHelper.Lerp(10.0f, 45.0f, MathHelper.Clamp(dist / steering.DockingAssistThreshold, 0.0f, 1.0f)));
1266 float midLength = (float)(Math.Cos(sector) * sectorLength);
1268 Vector2 midNormal =
new Vector2(-normalizedDockingDir.Y, normalizedDockingDir.X);
1270 DrawLine(spriteBatch, targetPortPos, targetPortPos + normalizedDockingDir * midLength, readyToDock ? positiveColor : staticLineColor, width: 2);
1271 DrawLine(spriteBatch, targetPortPos,
1272 targetPortPos + MathUtils.RotatePoint(normalizedDockingDir, sector) * sectorLength, staticLineColor, width: 2);
1273 DrawLine(spriteBatch, targetPortPos,
1274 targetPortPos + MathUtils.RotatePoint(normalizedDockingDir, -sector) * sectorLength, staticLineColor, width: 2);
1276 for (
float z = 0; z < 1.0f; z += 0.1f * zoom)
1278 Vector2 linePos = targetPortPos + normalizedDockingDir * midLength * z;
1279 DrawLine(spriteBatch, linePos + midNormal * 3.0f, linePos - midNormal * 3.0f, staticLineColor, width: 3);
1284 Color indicatorColor = positiveColor * 0.8f;
1286 float indicatorSize = (float)Math.Sin((
float)Timing.TotalTime * 5.0f) *
DisplayRadius * 0.75f;
1287 Vector2 midPoint = (sourcePortPos + targetPortPos) / 2.0f;
1288 DrawLine(spriteBatch,
1289 midPoint + Vector2.UnitY * indicatorSize,
1290 midPoint - Vector2.UnitY * indicatorSize,
1291 indicatorColor, width: 3);
1292 DrawLine(spriteBatch,
1293 midPoint + Vector2.UnitX * indicatorSize,
1294 midPoint - Vector2.UnitX * indicatorSize,
1295 indicatorColor, width: 3);
1299 float indicatorSector = sector * 0.75f;
1300 float indicatorSectorLength = (float)(midLength / Math.Cos(indicatorSector));
1303 (Math.Abs(diff.X) < steering.ActiveDockingSource.DistanceTolerance.X && Math.Abs(diff.Y) < steering.ActiveDockingSource.DistanceTolerance.Y) ||
1304 Vector2.Dot(normalizedDockingDir, MathUtils.RotatePoint(normalizedDockingDir, indicatorSector)) <
1305 Vector2.Dot(normalizedDockingDir, Vector2.Normalize(dockingDir));
1307 Color indicatorColor = withinSector ? positiveColor : negativeColor;
1308 indicatorColor *= 0.8f;
1310 DrawLine(spriteBatch, targetPortPos,
1311 targetPortPos + MathUtils.RotatePoint(normalizedDockingDir,indicatorSector) * indicatorSectorLength, indicatorColor, width: 3);
1312 DrawLine(spriteBatch, targetPortPos,
1313 targetPortPos + MathUtils.RotatePoint(normalizedDockingDir, -indicatorSector) * indicatorSectorLength, indicatorColor, width: 3);
1318 private void UpdateDisruptions(Vector2 pingSource,
float worldPingRadius)
1320 float worldPingRadiusSqr = worldPingRadius * worldPingRadius;
1322 disruptedDirections.Clear();
1324 for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex)
1326 foreach (LevelObject levelObject
in nearbyObjects)
1328 if (levelObject.ActivePrefab?.SonarDisruption <= 0.0f) {
continue; }
1330 float disruptionStrength = levelObject.ActivePrefab.SonarDisruption;
1331 Vector2 disruptionPos =
new Vector2(levelObject.Position.X, levelObject.Position.Y);
1333 float disruptionDist = Vector2.Distance(pingSource, disruptionPos);
1334 disruptedDirections.Add(((disruptionPos - pingSource) / disruptionDist, disruptionStrength));
1336 CreateBlipsForDisruption(disruptionPos, disruptionStrength);
1339 foreach (AITarget aiTarget
in AITarget.List)
1341 float disruption = aiTarget.Entity is
Character c && !c.IsUnconscious ? c.Params.SonarDisruption : aiTarget.SonarDisruption;
1342 if (disruption <= 0.0f || aiTarget.InDetectable) {
continue; }
1343 float distSqr = Vector2.DistanceSquared(aiTarget.WorldPosition, pingSource);
1344 if (distSqr > worldPingRadiusSqr) {
continue; }
1345 float disruptionDist = (float)Math.Sqrt(distSqr);
1346 disruptedDirections.Add(((aiTarget.WorldPosition - pingSource) / disruptionDist, aiTarget.SonarDisruption));
1347 CreateBlipsForDisruption(aiTarget.WorldPosition, disruption);
1351 void CreateBlipsForDisruption(Vector2 disruptionPos,
float disruptionStrength)
1353 disruptionStrength = Math.Min(disruptionStrength, 10.0f);
1354 Vector2 dir = disruptionPos - pingSource;
1355 for (
int i = 0; i < disruptionStrength * 10.0f; i++)
1357 Vector2 pos = disruptionPos + Rand.Vector(Rand.Range(0.0f, Level.GridCellSize * 4 * disruptionStrength));
1358 if (Vector2.Dot(pos - pingSource, -dir) > 1.0f - disruptionStrength) {
continue; }
1359 var blip =
new SonarBlip(
1361 MathHelper.Lerp(0.1f, 1.5f, Math.Min(disruptionStrength, 1.0f)),
1362 Rand.Range(0.2f, 1.0f + disruptionStrength),
1364 sonarBlips.Add(blip);
1373 Vector2 transducerCenter = GetTransducerPos();
1374 if (Vector2.DistanceSquared(worldPosition, transducerCenter) > range * range) {
return; }
1375 int blipCount = MathHelper.Clamp((
int)(explosion.
Attack.
Range / 100.0f), 0, 50);
1376 for (
int i = 0; i < blipCount; i++)
1379 worldPosition + Rand.Vector(Rand.Range(0.0f, explosion.
Attack.
Range)),
1381 Rand.Range(0.5f, 1.0f),
1386 int empBlipCount = MathHelper.Clamp((
int)(blipCount * explosion.
EmpStrength), 10, 50);
1387 for (
int i = 0; i < empBlipCount; i++)
1389 Vector2 dir = Rand.Vector(1.0f);
1390 var longRangeBlip =
new SonarBlip(worldPosition, Rand.Range(1.9f, 2.1f), Rand.Range(1.0f, 1.5f),
BlipType.LongRange)
1392 Velocity = dir * MathUtils.Round(Rand.Range(4000.0f, 6000.0f), 1000.0f),
1393 Rotation = (float)Math.Atan2(-dir.Y, dir.X)
1395 longRangeBlip.
Size.Y *= 4.0f;
1396 sonarBlips.Add(longRangeBlip);
1401 private void Ping(Vector2 pingSource, Vector2 transducerPos,
float pingRadius,
float prevPingRadius,
float displayScale,
float range,
bool passive,
1402 float pingStrength = 1.0f,
AITarget needsToBeInSector =
null)
1404 float prevPingRadiusSqr = prevPingRadius * prevPingRadius;
1405 float pingRadiusSqr = pingRadius * pingRadius;
1413 pingSource, transducerPos,
1414 pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive, needsToBeInSector: needsToBeInSector);
1419 pingSource, transducerPos,
1420 pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive, needsToBeInSector: needsToBeInSector);
1425 pingSource, transducerPos,
1426 pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive, needsToBeInSector: needsToBeInSector);
1431 pingSource, transducerPos,
1432 pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive, needsToBeInSector: needsToBeInSector);
1442 if (connectedSubs.Contains(submarine)) {
continue; }
1451 CreateBlipsForSubmarineWalls(submarine, pingSource, transducerPos, pingRadius, prevPingRadius, range, passive);
1457 Vector2 start = ConvertUnits.ToDisplayUnits(submarine.
HullVertices[i]);
1462 start += Rand.Vector(500.0f);
1463 end += Rand.Vector(500.0f);
1469 pingSource, transducerPos,
1470 pingRadius, prevPingRadius,
1471 200.0f, 2.0f, range, 1.0f, passive,
1472 needsToBeInSector: needsToBeInSector);
1478 if (Level.Loaded.Size.Y - pingSource.Y < range)
1481 new Vector2(pingSource.X - range, Level.Loaded.Size.Y),
1482 new Vector2(pingSource.X + range, Level.Loaded.Size.Y),
1483 pingSource, transducerPos,
1484 pingRadius, prevPingRadius,
1485 250.0f, 150.0f, range, pingStrength, passive,
1486 needsToBeInSector: needsToBeInSector);
1488 if (pingSource.Y - Level.Loaded.BottomPos < range)
1491 new Vector2(pingSource.X - range, Level.Loaded.BottomPos),
1492 new Vector2(pingSource.X + range, Level.Loaded.BottomPos),
1493 pingSource, transducerPos,
1494 pingRadius, prevPingRadius,
1495 250.0f, 150.0f, range, pingStrength, passive,
1496 needsToBeInSector: needsToBeInSector);
1504 if (!edge.IsSolid) {
continue; }
1507 float cellDot = Vector2.Dot((edge.Center + cell.Translation) - pingSource, edge.GetNormal(cell));
1508 if (cellDot > 0) {
continue; }
1510 float facingDot = Vector2.Dot(
1511 Vector2.Normalize(edge.Point1 - edge.Point2),
1512 Vector2.Normalize(cell.Center - pingSource));
1515 edge.Point1 + cell.Translation,
1516 edge.Point2 + cell.Translation,
1517 pingSource, transducerPos,
1518 pingRadius, prevPingRadius,
1519 350.0f, 3.0f * (Math.Abs(facingDot) + 1.0f), range, pingStrength, passive,
1521 needsToBeInSector: needsToBeInSector);
1531 float pointDist = ((
item.
WorldPosition - pingSource) * displayScale).LengthSquared();
1532 if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr)
1534 var blip =
new SonarBlip(
1538 if (!IsVisible(blip)) {
continue; }
1539 sonarBlips.Add(blip);
1544 foreach (Character c
in Character.CharacterList)
1546 if (c.AnimController.CurrentHull !=
null || !c.Enabled) {
continue; }
1547 if (!c.IsUnconscious && c.Params.HideInSonar) {
continue; }
1548 if (c.InDetectable) {
continue; }
1551 if (c.AnimController.SimplePhysicsEnabled)
1553 float pointDist = ((c.WorldPosition - pingSource) * displayScale).LengthSquared();
1556 if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr)
1558 var blip =
new SonarBlip(
1560 MathHelper.Clamp(c.Mass, 0.1f, pingStrength),
1561 MathHelper.Clamp(c.Mass * 0.03f, 0.1f, 2.0f));
1562 if (!IsVisible(blip)) {
continue; }
1563 sonarBlips.Add(blip);
1564 HintManager.OnSonarSpottedCharacter(
Item, c);
1569 foreach (Limb limb
in c.AnimController.Limbs)
1571 if (!limb.body.Enabled) {
continue; }
1573 float pointDist = ((limb.WorldPosition - pingSource) * displayScale).LengthSquared();
1576 if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr)
1578 var blip =
new SonarBlip(
1579 limb.WorldPosition + Rand.Vector(limb.Mass / 10.0f),
1580 MathHelper.Clamp(limb.Mass, 0.1f, pingStrength),
1581 MathHelper.Clamp(limb.Mass * 0.1f, 0.1f, 2.0f));
1582 if (!IsVisible(blip)) {
continue; }
1583 sonarBlips.Add(blip);
1584 HintManager.OnSonarSpottedCharacter(
Item, c);
1589 bool IsVisible(SonarBlip blip)
1591 if (!passive && !CheckBlipVisibility(blip, transducerPos)) {
return false; }
1592 if (needsToBeInSector !=
null)
1594 if (!needsToBeInSector.IsWithinSector(blip.Position)) {
return false; }
1600 private void CreateBlipsForLine(Vector2 point1, Vector2 point2, Vector2 pingSource, Vector2 transducerPos,
float pingRadius,
float prevPingRadius,
1601 float lineStep,
float zStep,
float range,
float pingStrength,
bool passive,
BlipType blipType =
BlipType.Default, AITarget needsToBeInSector =
null)
1606 float length = (point1 - point2).Length();
1607 Vector2 lineDir = (point2 - point1) / length;
1608 for (
float x = 0; x < length; x += lineStep * Rand.Range(0.8f, 1.2f))
1610 if (Rand.Int(sonarBlips.Count) > 500) {
continue; }
1612 Vector2 point = point1 + lineDir * x;
1615 Vector2 transducerDiff = point - transducerPos;
1616 Vector2 transducerDisplayDiff = transducerDiff *
DisplayScale / zoom;
1620 Vector2 pointDiff = point - pingSource;
1621 Vector2 displayPointDiff = pointDiff *
DisplayScale / zoom;
1622 float displayPointDistSqr = displayPointDiff.LengthSquared();
1623 if (displayPointDistSqr < prevPingRadius * prevPingRadius || displayPointDistSqr > pingRadius * pingRadius) {
continue; }
1626 float transducerDist = transducerDiff.Length();
1627 Vector2 pingDirection = transducerDiff / transducerDist;
1628 bool disrupted =
false;
1629 foreach ((Vector2 disruptPos,
float disruptStrength) in disruptedDirections)
1631 float dot = Vector2.Dot(pingDirection, disruptPos);
1632 if (dot > 1.0f - disruptStrength)
1638 if (disrupted) {
continue; }
1640 float displayPointDist = (float)Math.Sqrt(displayPointDistSqr);
1641 float alpha = pingStrength * Rand.Range(1.5f, 2.0f);
1644 Vector2 pos = point + Rand.Vector(150.0f / zoom) + pingDirection * z /
DisplayScale;
1645 float fadeTimer = alpha * (1.0f - displayPointDist / range);
1647 if (needsToBeInSector !=
null)
1649 if (!needsToBeInSector.IsWithinSector(pos)) {
continue; }
1652 var blip =
new SonarBlip(pos, fadeTimer, 1.0f + ((displayPointDist + z) /
DisplayRadius), blipType);
1653 if (!passive && !CheckBlipVisibility(blip, transducerPos)) {
continue; }
1655 int minDist = (int)(200 / zoom);
1656 sonarBlips.RemoveAll(b => b.FadeTimer < fadeTimer && Math.Abs(pos.X - b.Position.X) < minDist && Math.Abs(pos.Y - b.Position.Y) < minDist);
1658 sonarBlips.Add(blip);
1659 zStep += 0.5f / zoom;
1663 alpha = Math.Min(alpha - 0.5f, 1.5f);
1670 if (alpha < 0) {
break; }
1675 private void CreateBlipsForSubmarineWalls(Submarine sub, Vector2 pingSource, Vector2 transducerPos,
float pingRadius,
float prevPingRadius,
float range,
bool passive)
1677 foreach (Structure structure
in Structure.WallList)
1679 if (structure.Submarine != sub) {
continue; }
1680 CreateBlips(structure.IsHorizontal, structure.WorldPosition, structure.WorldRect);
1682 foreach (var door
in Door.DoorList)
1684 if (door.Item.Submarine != sub || door.IsOpen) {
continue; }
1685 CreateBlips(door.IsHorizontal, door.Item.WorldPosition, door.Item.WorldRect,
BlipType.Door);
1688 void CreateBlips(
bool isHorizontal, Vector2 worldPos, Rectangle worldRect,
BlipType blipType =
BlipType.Default)
1690 Vector2 point1, point2;
1693 point1 =
new Vector2(worldRect.X, worldPos.Y);
1694 point2 =
new Vector2(worldRect.Right, worldPos.Y);
1698 point1 =
new Vector2(worldPos.X, worldRect.Y);
1699 point2 =
new Vector2(worldPos.X, worldRect.Y - worldRect.Height);
1704 pingSource, transducerPos,
1705 pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive, blipType);
1709 private bool CheckBlipVisibility(SonarBlip blip, Vector2 transducerPos)
1711 Vector2 pos = (blip.Position - transducerPos) *
DisplayScale;
1714 float posDistSqr = pos.LengthSquared();
1717 blip.FadeTimer = 0.0f;
1721 Vector2 dir = pos / (float)Math.Sqrt(posDistSqr);
1722 if (currentPingIndex != -1 && activePings[currentPingIndex].IsDirectional)
1724 if (Vector2.Dot(activePings[currentPingIndex].Direction, dir) < DirectionalPingDotProduct)
1726 blip.FadeTimer = 0.0f;
1736 private bool CheckResourceMarkerVisibility(Vector2 resourcePos, Vector2 transducerPos)
1738 var distSquared = Vector2.DistanceSquared(transducerPos, resourcePos);
1743 if (currentPingIndex != -1 && activePings[currentPingIndex].IsDirectional)
1745 var pos = (resourcePos - transducerPos) *
DisplayScale;
1747 var length = pos.Length();
1748 var dir = pos / length;
1749 if (Vector2.Dot(activePings[currentPingIndex].Direction, dir) < DirectionalPingDotProduct)
1757 private void DrawBlip(SpriteBatch spriteBatch, SonarBlip blip, Vector2 transducerPos, Vector2 center,
float strength,
float blipScale)
1759 strength = MathHelper.Clamp(strength, 0.0f, 1.0f);
1763 Vector2 pos = (blip.Position - transducerPos) *
DisplayScale;
1766 if (Rand.Range(0.5f, 2.0f) < distort) { pos.X = -pos.X; }
1767 if (Rand.Range(0.5f, 2.0f) < distort) { pos.Y = -pos.Y; }
1769 float posDistSqr = pos.LengthSquared();
1772 blip.FadeTimer = 0.0f;
1776 if (sonarBlip ==
null)
1778 GUI.DrawRectangle(spriteBatch, center + pos, Vector2.One * 4, Color.Magenta,
true);
1782 Vector2 dir = pos / (float)Math.Sqrt(posDistSqr);
1783 Vector2 normal =
new Vector2(dir.Y, -dir.X);
1784 float scale = (strength + 3.0f) * blip.Scale * blipScale;
1785 Color color = ToolBox.GradientLerp(strength, blipColorGradient[blip.BlipType]);
1787 sonarBlip.Draw(spriteBatch, center + pos, color * blip.Alpha, sonarBlip.Origin, blip.Rotation ?? MathUtils.VectorToAngle(pos),
1788 blip.Size * scale * 0.5f, SpriteEffects.None, 0);
1790 pos += Rand.Range(0.0f, 1.0f) * dir + Rand.Range(-scale, scale) * normal;
1792 sonarBlip.Draw(spriteBatch, center + pos, color * 0.5f * blip.Alpha, sonarBlip.Origin, 0, scale, SpriteEffects.None, 0);
1795 private void DrawMarker(SpriteBatch spriteBatch,
string label, Identifier iconIdentifier,
object targetIdentifier, Vector2 worldPosition, Vector2 transducerPosition,
float scale, Vector2 center,
float radius,
1796 bool onlyShowTextOnMouseOver =
false)
1798 float linearDist = Vector2.Distance(worldPosition, transducerPosition);
1799 float dist = linearDist;
1800 if (linearDist >
Range)
1802 if (markerDistances.TryGetValue(targetIdentifier, out CachedDistance cachedDistance))
1804 if (cachedDistance.ShouldUpdateDistance(transducerPosition, worldPosition))
1806 markerDistances.Remove(targetIdentifier);
1807 CalculateDistance();
1811 dist = Math.Max(cachedDistance.Distance, linearDist);
1816 CalculateDistance();
1820 void CalculateDistance()
1822 pathFinder ??=
new PathFinder(WayPoint.WayPointList,
false);
1823 var path = pathFinder.
FindPath(ConvertUnits.ToSimUnits(transducerPosition), ConvertUnits.ToSimUnits(worldPosition));
1824 if (!path.Unreachable)
1826 var cachedDistance =
new CachedDistance(transducerPosition, worldPosition, path.TotalLength, Timing.TotalTime + Rand.Range(1.0f, 5.0f));
1827 markerDistances.Add(targetIdentifier, cachedDistance);
1828 dist = path.TotalLength;
1832 var cachedDistance =
new CachedDistance(transducerPosition, worldPosition, linearDist, Timing.TotalTime + Rand.Range(4.0f, 7.0f));
1833 markerDistances.Add(targetIdentifier, cachedDistance);
1837 Vector2 position = worldPosition - transducerPosition;
1840 position.Y = -position.Y;
1842 float textAlpha = MathHelper.Clamp(1.5f - dist / 50000.0f, 0.5f, 1.0f);
1844 Vector2 dir = Vector2.Normalize(position);
1845 Vector2 markerPos = (linearDist * scale > radius) ? dir * radius : position;
1846 markerPos += center;
1848 markerPos.X = (int)markerPos.X;
1849 markerPos.Y = (
int)markerPos.Y;
1852 if (!onlyShowTextOnMouseOver)
1854 if (linearDist * scale < radius)
1856 float normalizedDist = linearDist * scale / radius;
1857 alpha = Math.Max(normalizedDist - 0.4f, 0.0f);
1859 float mouseDist = Vector2.Distance(PlayerInput.MousePosition, markerPos);
1860 float hoverThreshold = 150.0f;
1861 if (mouseDist < hoverThreshold)
1863 alpha += (hoverThreshold - mouseDist) / hoverThreshold;
1869 float mouseDist = Vector2.Distance(PlayerInput.MousePosition, markerPos);
1876 if (iconIdentifier ==
null || !targetIcons.TryGetValue(iconIdentifier, out var iconInfo) || iconInfo.Item1 ==
null)
1878 GUI.DrawRectangle(spriteBatch,
new Rectangle((
int)markerPos.X - 3, (int)markerPos.Y - 3, 6, 6), markerColor, thickness: 2);
1882 iconInfo.Item1.Draw(spriteBatch, markerPos, iconInfo.Item2);
1885 if (alpha <= 0.0f) {
return; }
1887 string wrappedLabel = ToolBox.WrapText(label, 150, GUIStyle.SmallFont.Value);
1888 wrappedLabel +=
"\n" + ((int)(dist * Physics.DisplayToRealWorldRatio) +
" m");
1890 Vector2 labelPos = markerPos;
1891 Vector2 textSize = GUIStyle.SmallFont.MeasureString(wrappedLabel);
1894 if (
GuiFrame !=
null && (dir.X < 0.0f || labelPos.X + textSize.X + 10 >
GuiFrame.
Rect.X) && labelPos.X - textSize.X > 0)
1896 labelPos.X -= textSize.X + 10;
1899 GUI.DrawString(spriteBatch,
1900 new Vector2(labelPos.X + 10, labelPos.Y),
1902 Color.LightBlue * textAlpha * alpha, Color.Black * textAlpha * 0.8f * alpha,
1903 2, GUIStyle.SmallFont);
1908 base.RemoveComponentSpecific();
1909 sonarBlip?.Remove();
1911 directionalPingCircle?.
Remove();
1913 screenBackground?.
Remove();
1916 foreach (var t
in targetIcons.Values)
1920 targetIcons.Clear();
1922 MineralClusters =
null;
1928 if (currentMode ==
Mode.Active)
1932 if (useDirectionalPing)
1934 float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
1935 msg.
WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
1947 bool directionalPing = useDirectionalPing;
1948 float directionT = 0.0f;
1954 if (directionalPing)
1973 zoom = MathHelper.Lerp(MinZoom, MaxZoom, zoomT);
1974 if (directionalPing)
1976 float pingAngle = MathHelper.Lerp(0.0f, MathHelper.TwoPi, directionT);
1977 pingDirection =
new Vector2((
float)Math.Cos(pingAngle), (
float)Math.Sin(pingAngle));
1979 useDirectionalPing = directionalModeSwitch.
Selected = directionalPing;
1981 if (mineralScannerSwitch !=
null)
1983 mineralScannerSwitch.
Selected = mineralScanner;
1988 private void UpdateGUIElements()
1992 passiveTickBox.
Selected = !isActive;
1994 directionalModeSwitch.
Selected = useDirectionalPing;
1995 if (mineralScannerSwitch !=
null)
2018 Size =
new Vector2(0.5f, 1.0f);
static List< AITarget > List
readonly CharacterParams Params
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
static readonly List< Character > CharacterList
static Character? Controlled
readonly AnimController AnimController
virtual Vector2 WorldPosition
Explosions are area of effect attacks that can damage characters, items and structures.
float EmpStrength
Strength of the EMP effect created by the explosion.
GUIComponent GetChild(int index)
bool IsParentOf(GUIComponent component, bool recursive=true)
GUIComponent GetChildByUserData(object obj)
RectTransform RectTransform
GUIComponent that can be used to render custom content on the UI
void OverrideTextColor(Color color)
Overrides the color for all the states.
static void AutoScaleAndNormalize(params GUITextBlock[] textBlocks)
Set the text scale of the GUITextBlocks so that they all use the same scale and can fit the text with...
static IReadOnlyCollection< Item > SonarVisibleItems
Items whose ItemPrefab.SonarSize is larger than 0
void StartDelayedCorrection(IReadMessage buffer, float sendingTime, bool waitForMidRoundSync=false)
const float CorrectionDelay
SonarBlip(Vector2 pos, float fadeTimer, float scale, Sonar.BlipType blipType=Sonar.BlipType.Default)
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
void ClientEventRead(IReadMessage msg, float sendingTime)
override void OnResolutionChanged()
override void RemoveComponentSpecific()
static readonly Vector2 controlBoxOffset
float DisplayScale
Current scale of the display, taking zoom into account. In other words, the scaling factor of world c...
GUIButton SonarModeSwitch
static readonly Vector2 controlBoxSize
static Vector2 GUISizeCalculation
override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam)
bool DetectSubmarineWalls
void RegisterExplosion(Explosion explosion, Vector2 worldPosition)
override bool RecreateGUIOnResolutionChange
void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData=null)
override void CreateGUI()
Overload this method and implement. The method is automatically called when the resolution changes.
float DockingAssistThreshold
DockingPort ActiveDockingSource
LevelObjectManager LevelObjectManager
List< ClusterLocation > AbyssResources
List< PathPoint > PathPoints
List< LevelTrigger > Triggers
IEnumerable< LevelObject > GetAllObjects()
bool ForceFalloff
does the force diminish by distance
Vector2 GetWaterFlowVelocity(Vector2 viewPosition)
Identifier InfectIdentifier
LocalizedString Fallback(LocalizedString fallback, bool useDefaultLanguageIfFound=true)
Use this text instead if the original text cannot be found.
SteeringPath FindPath(Vector2 start, Vector2 end, Submarine hostSub=null, string errorMsgStr=null, float minGapSize=0, Func< PathNode, bool > startNodeFilter=null, Func< PathNode, bool > endNodeFilter=null, Func< PathNode, bool > nodeFilter=null, bool checkVisibility=true)
void Draw(ISpriteBatch spriteBatch, Vector2 pos, float rotate=0.0f, float scale=1.0f, SpriteEffects spriteEffect=SpriteEffects.None)
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
override Vector2? WorldPosition
IEnumerable< Submarine > GetConnectedSubs()
Returns a list of all submarines that are connected to this one via docking ports,...
static List< Submarine > Loaded
List< Vector2 >? HullVertices
Rectangle GetDockedBorders(bool allowDifferentTeam=true)
Returns a rect that contains the borders of this sub and all subs docked to it, excluding outposts
OutpostGenerationParams OutpostGenerationParams
Highlights an UI element of some kind. Generally used in tutorials.
Interface for entities that the clients can send events to the server
Single ReadRangedSingle(Single min, Single max, int bitCount)
Interface for entities that the server can send events to the clients
void WriteRangedSingle(Single val, Single min, Single max, int bitCount)
void WriteBoolean(bool val)