Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/Source/Items/Components/Machines/Sonar.cs
T
Joonas Rikkonen c2e8263927 f9e8100...ccacceb
commit ccacceb16a184f00ecd384eede64ca9c4fab08a0
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 14:05:59 2019 +0200

    NetEntityEventManager checks the length of the event data (and logs an error if it's too long) before checking if there's still room to keep writing events in the packet. Checking the available room first could lead to situations where an excessively large event can't fit to any packet, "soft-locking" the EventManager without any error messages.

commit 5ac8259372aa900adc724aa4da1fd81af41ca195
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:41:52 2019 +0200

    Don't display disabled limbs on sonar (i.e. severed limbs that have "faded out")

commit 5f84df73ad86be96f3678c450351b3905e7317a4
Merge: b981f1635 dc429d6c4
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:41:16 2019 +0200

    Merge branch 'dev' of https://github.com/Regalis11/Barotrauma-development into dev

commit b981f163575b2bfc9a83b9925c94eca19b9d4554
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:36:19 2019 +0200

    Multiplayer campaign fixes:
    - Server uses a different temp folder to decompress save/sub files into than the clients. Should fix files occasionally getting corrupted and exceptions when trying to read the files when hosting a server from the main executable.
    - Some additional debug logging.
    - Use the base names of the adjacent locations as level seeds (i.e. "Vorta" instead of "Vorta Outpost"). The levels should not change when the type (and full name) of the location changes.

commit 42c5d18df77fc7acd5873d8e25f20bdd31b1ed76
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:31:06 2019 +0200

    Don't transfer files through the network when sending them to the owner of the server (i.e. a client hosting directly from the main executable), but simply tell the client where the file is located.

commit dc429d6c450f4893fe29c51d3c830527e587a871
Author: Daniel Asteljoki <daniel.asteljoki@gmail.com>
Date:   Mon Mar 25 13:30:26 2019 +0200

    Added labels next to periscopes in Humpback and Dugong

commit 789f02a87a2917dd2ae378f136cbe8dd3236c60d
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 13:29:29 2019 +0200

    If loading a submarine fails, wait a bit and retry up to 4 times. Fixes loading occasionally failing when running multiple instances of the game from the same directory.

commit be9ea3a58832992b6226917117247e1bf1efeff9
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 11:03:36 2019 +0200

    Fixed a bunch of disconnection messages being in an incorrect format & DisconnectUnauthClient not getting the messages from the xml

commit c6f744b4d6b3520720010f5cd4f22a25b42bfc8b
Author: Joonas Rikkonen <poe.regalis@gmail.com>
Date:   Mon Mar 25 10:43:10 2019 +0200

    Log entity event errors into server logs when verbose logging is enabled
2019-03-25 14:30:00 +02:00

1074 lines
48 KiB
C#

using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Sonar : Powered, IServerSerializable, IClientSerializable
{
enum Mode
{
Active,
Passive
};
private bool unsentChanges;
private float networkUpdateTimer;
private GUITickBox activeTickBox, passiveTickBox;
private GUITextBlock signalWarningText;
private GUIScrollBar zoomSlider;
private GUITickBox directionalTickBox;
private GUIScrollBar directionalSlider;
private GUIComponent activeControlsContainer;
private GUICustomComponent sonarView;
private float displayBorderSize;
private List<SonarBlip> sonarBlips;
private float prevPingRadius;
private float prevPassivePingRadius;
private Vector2 center;
private float displayScale;
private float zoomSqrt;
//Vector2 = vector from the ping source to the position of the disruption
//float = strength of the disruption, between 0-1
List<Pair<Vector2, float>> disruptedDirections = new List<Pair<Vector2, float>>();
private static Color[] blipColorGradient =
{
Color.TransparentBlack,
new Color(0, 50, 160),
new Color(0, 133, 166),
new Color(2, 159, 30),
new Color(255, 255, 255)
};
public Vector2 DisplayOffset { get; private set; }
public float DisplayRadius { get; private set; }
partial void InitProjSpecific(XElement element)
{
sonarBlips = new List<SonarBlip>();
int viewSize = (int)Math.Min(GuiFrame.Rect.Width - 150, GuiFrame.Rect.Height * 0.9f);
sonarView = new GUICustomComponent(new RectTransform(new Point(viewSize), GuiFrame.RectTransform, Anchor.CenterLeft),
(spriteBatch, guiCustomComponent) => { DrawSonar(spriteBatch, guiCustomComponent.Rect); }, null);
var controlContainer = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.35f), GuiFrame.RectTransform, Anchor.TopLeft)
{ MinSize = new Point(150, 0), AbsoluteOffset = new Point((int)(viewSize * 0.9f), 0) }, "SonarFrame");
controlContainer.RectTransform.SetAsFirstChild();
var paddedControlContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), controlContainer.RectTransform, Anchor.Center))
{
RelativeSpacing = 0.03f,
Stretch = true
};
passiveTickBox = new GUITickBox(new RectTransform(new Vector2(0.3f, 0.2f), paddedControlContainer.RectTransform), TextManager.Get("SonarPassive"), style: "GUIRadioButton")
{
ToolTip = TextManager.Get("SonarTipPassive"),
Selected = true
};
activeTickBox = new GUITickBox(new RectTransform(new Vector2(0.3f, 0.2f), paddedControlContainer.RectTransform), TextManager.Get("SonarActive"), style: "GUIRadioButton")
{
ToolTip = TextManager.Get("SonarTipActive"),
OnSelected = (GUITickBox box) =>
{
IsActive = box.Selected;
if (GameMain.Client != null)
{
unsentChanges = true;
correctionTimer = CorrectionDelay;
}
return true;
}
};
var zoomContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), paddedControlContainer.RectTransform), isHorizontal: true);
new GUITextBlock(new RectTransform(new Vector2(0.35f, 1.0f), zoomContainer.RectTransform), TextManager.Get("SonarZoom"), font: GUI.SmallFont)
{
Padding = Vector4.Zero
};
zoomSlider = new GUIScrollBar(new RectTransform(new Vector2(0.65f, 1.0f), zoomContainer.RectTransform), barSize: 0.1f, isHorizontal: true)
{
OnMoved = (scrollbar, scroll) =>
{
zoom = MathHelper.Lerp(MinZoom, MaxZoom, scroll);
if (GameMain.Client != null)
{
unsentChanges = true;
correctionTimer = CorrectionDelay;
}
return true;
}
};
var activeControls = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.6f), paddedControlContainer.RectTransform) { RelativeOffset = new Vector2(0.1f, 0.0f) }, "InnerFrame");
activeControlsContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), activeControls.RectTransform, Anchor.Center))
{
RelativeSpacing = 0.03f,
Stretch = true
};
directionalTickBox = new GUITickBox(new RectTransform(new Vector2(0.3f, 0.3f), activeControlsContainer.RectTransform), TextManager.Get("SonarDirectionalPing"))
{
OnSelected = (tickBox) =>
{
useDirectionalPing = tickBox.Selected;
if (GameMain.Client != null)
{
unsentChanges = true;
correctionTimer = CorrectionDelay;
}
return true;
}
};
directionalSlider = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.3f), activeControlsContainer.RectTransform), barSize: 0.1f, isHorizontal: true)
{
OnMoved = (scrollbar, scroll) =>
{
float pingAngle = MathHelper.Lerp(0.0f, MathHelper.TwoPi, scroll);
pingDirection = new Vector2((float)Math.Cos(pingAngle), (float)Math.Sin(pingAngle));
if (GameMain.Client != null)
{
unsentChanges = true;
correctionTimer = CorrectionDelay;
}
return true;
}
};
signalWarningText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), paddedControlContainer.RectTransform), "", Color.Orange, textAlignment: Alignment.Center);
GUIRadioButtonGroup sonarMode = new GUIRadioButtonGroup();
sonarMode.AddRadioButton(Mode.Active, activeTickBox);
sonarMode.AddRadioButton(Mode.Passive, passiveTickBox);
sonarMode.Selected = Mode.Passive;
GuiFrame.CanBeFocused = false;
}
public override void OnItemLoaded()
{
zoomSlider.BarScroll = MathUtils.InverseLerp(MinZoom, MaxZoom, zoom);
//make the sonarView customcomponent render the steering view so it gets drawn in front of the sonar
item.GetComponent<Steering>()?.AttachToSonarHUD(sonarView);
}
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{
if (GameMain.Client != null)
{
if (unsentChanges)
{
if (networkUpdateTimer <= 0.0f)
{
item.CreateClientEvent(this);
correctionTimer = CorrectionDelay;
networkUpdateTimer = 0.1f;
unsentChanges = false;
}
}
networkUpdateTimer -= deltaTime;
}
if (sonarView.Rect.Contains(PlayerInput.MousePosition))
{
float scrollSpeed = PlayerInput.ScrollWheelSpeed / 1000.0f;
if (Math.Abs(scrollSpeed) > 0.0001f)
{
zoomSlider.BarScroll += PlayerInput.ScrollWheelSpeed / 1000.0f;
zoomSlider.OnMoved(zoomSlider, zoomSlider.BarScroll);
}
}
float distort = 1.0f - item.Condition / item.MaxCondition;
for (int i = sonarBlips.Count - 1; i >= 0; i--)
{
sonarBlips[i].FadeTimer -= deltaTime * MathHelper.Lerp(0.5f, 2.0f, distort);
sonarBlips[i].Position += sonarBlips[i].Velocity * deltaTime;
if (sonarBlips[i].FadeTimer <= 0.0f) sonarBlips.RemoveAt(i);
}
foreach (GUIComponent component in activeControlsContainer.Parent.GetAllChildren())
{
if (component is GUITextBlock textBlock)
{
textBlock.TextColor = IsActive ? textBlock.Style.textColor : textBlock.Style.textColor * 0.5f;
}
}
//sonar view can only get focus when the cursor is inside the circle
sonarView.CanBeFocused =
Vector2.DistanceSquared(sonarView.Rect.Center.ToVector2(), PlayerInput.MousePosition) <
(sonarView.Rect.Width / 2 * sonarView.Rect.Width / 2);
if (UseTransducers && connectedTransducers.Count == 0)
{
return;
}
Vector2 transducerCenter = UseTransducers ? GetTransducerCenter() : item.WorldPosition;
transducerCenter += DisplayOffset;
if (Level.Loaded != null)
{
Dictionary<LevelTrigger, Vector2> levelTriggerFlows = new Dictionary<LevelTrigger, Vector2>();
foreach (LevelObject levelObject in Level.Loaded.LevelObjectManager.GetAllObjects(transducerCenter, range * pingState / zoom))
{
//gather all nearby triggers that are causing the water to flow into the dictionary
foreach (LevelTrigger trigger in levelObject.Triggers)
{
Vector2 flow = trigger.GetWaterFlowVelocity();
//ignore ones that are barely doing anything (flow^2 < 1)
if (flow.LengthSquared() > 1.0f) levelTriggerFlows.Add(trigger, flow);
}
}
foreach (KeyValuePair<LevelTrigger, Vector2> triggerFlow in levelTriggerFlows)
{
LevelTrigger trigger = triggerFlow.Key;
Vector2 flow = triggerFlow.Value;
float flowMagnitude = flow.Length();
if (Rand.Range(0.0f, 1.0f) < flowMagnitude / 1000.0f)
{
float edgeDist = Rand.Range(0.0f, 1.0f);
Vector2 blipPos = trigger.WorldPosition + Rand.Vector(trigger.ColliderRadius * edgeDist);
Vector2 blipVel = flow;
if (trigger.ForceFalloff) flow *= (1.0f - edgeDist);
//go through other triggers in range and add the flows of the ones that the blip is inside
foreach (KeyValuePair<LevelTrigger, Vector2> triggerFlow2 in levelTriggerFlows)
{
LevelTrigger trigger2 = triggerFlow2.Key;
if (trigger2 != trigger && Vector2.DistanceSquared(blipPos, trigger2.WorldPosition) < trigger2.ColliderRadius * trigger2.ColliderRadius)
{
Vector2 trigger2flow = triggerFlow2.Value;
if (trigger2.ForceFalloff) trigger2flow *= (1.0f - Vector2.Distance(blipPos, trigger2.WorldPosition) / trigger2.ColliderRadius);
blipVel += trigger2flow;
}
}
var flowBlip = new SonarBlip(blipPos, Rand.Range(0.5f, 1.0f), 1.0f)
{
Velocity = blipVel * Rand.Range(1.0f, 5.0f),
Size = new Vector2(MathHelper.Lerp(0.4f, 5f, flowMagnitude / 500.0f), 0.2f),
Rotation = (float)Math.Atan2(-blipVel.Y, blipVel.X)
};
sonarBlips.Add(flowBlip);
}
}
}
if (IsActive)
{
float pingRadius = DisplayRadius * pingState / zoom;
UpdateDisruptions(transducerCenter, pingRadius / displayScale, prevPingRadius / displayScale);
Ping(transducerCenter, transducerCenter,
pingRadius, prevPingRadius, displayScale, range / zoom, passive: false, pingStrength: 2.0f);
prevPingRadius = pingRadius;
return;
}
float passivePingRadius = (float)(Timing.TotalTime % 1.0f);
if (passivePingRadius > 0.0f)
{
disruptedDirections.Clear();
foreach (AITarget t in AITarget.List)
{
if (t.SoundRange <= 0.0f || !t.Enabled) { continue; }
float distSqr = Vector2.DistanceSquared(t.WorldPosition, transducerCenter);
if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; }
float dist = (float)Math.Sqrt(distSqr);
if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range)
{
Ping(t.WorldPosition, transducerCenter,
Math.Min(t.SoundRange, range * 0.5f) * displayScale, 0, displayScale, Math.Min(t.SoundRange, range * 0.5f),
passive: true, pingStrength: 0.5f);
sonarBlips.Add(new SonarBlip(t.WorldPosition, 1.0f, 1.0f));
}
}
}
prevPassivePingRadius = passivePingRadius;
}
private void DrawSonar(SpriteBatch spriteBatch, Rectangle rect)
{
displayBorderSize = 0.2f;
center = rect.Center.ToVector2();
DisplayRadius = (rect.Width / 2.0f) * (1.0f - displayBorderSize);
displayScale = DisplayRadius / range * zoom;
if (screenBackground != null)
{
screenBackground.Draw(spriteBatch, center, 0.0f, rect.Width / screenBackground.size.X);
}
if (IsActive)
{
if (isLastPingDirectional && directionalPingCircle != null)
{
directionalPingCircle.Draw(spriteBatch, center, Color.White * (1.0f - pingState),
rotate: MathUtils.VectorToAngle(lastPingDirection),
scale: (DisplayRadius / directionalPingCircle.size.X) * pingState);
}
else
{
pingCircle.Draw(spriteBatch, center, Color.White * (1.0f - pingState), 0.0f, (DisplayRadius * 2 / pingCircle.size.X) * pingState);
}
}
float signalStrength = 1.0f;
if (UseTransducers)
{
signalStrength = 0.0f;
foreach (ConnectedTransducer connectedTransducer in connectedTransducers)
{
signalStrength = Math.Max(signalStrength, connectedTransducer.SignalStrength);
}
}
Vector2 transducerCenter = UseTransducers && connectedTransducers.Count > 0 ? GetTransducerCenter() : item.WorldPosition;
if (item.Submarine != null && !DetectSubmarineWalls)
{
DrawDockingPorts(spriteBatch, transducerCenter, signalStrength);
transducerCenter += DisplayOffset;
DrawOwnSubmarineBorders(spriteBatch, transducerCenter, signalStrength);
}
else
{
DisplayOffset = Vector2.Zero;
}
if (sonarBlips.Count > 0)
{
zoomSqrt = (float)Math.Sqrt(zoom);
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
foreach (SonarBlip sonarBlip in sonarBlips)
{
DrawBlip(spriteBatch, sonarBlip, transducerCenter, center, sonarBlip.FadeTimer / 2.0f * signalStrength);
}
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
}
if (useDirectionalPing && IsActive)
{
Vector2 sector1 = MathUtils.RotatePointAroundTarget(pingDirection * DisplayRadius, Vector2.Zero, DirectionalPingSector * 0.5f);
Vector2 sector2 = MathUtils.RotatePointAroundTarget(pingDirection * DisplayRadius, Vector2.Zero, -DirectionalPingSector * 0.5f);
GUI.DrawLine(spriteBatch, center, center + sector1, Color.LightCyan * 0.2f, width: 3);
GUI.DrawLine(spriteBatch, center, center + sector2, Color.LightCyan * 0.2f, width: 3);
}
if (GameMain.DebugDraw)
{
GUI.DrawString(spriteBatch, rect.Location.ToVector2(), sonarBlips.Count.ToString(), Color.White);
}
if (screenOverlay != null)
{
screenOverlay.Draw(spriteBatch, center, 0.0f, rect.Width / screenOverlay.size.X);
}
if (signalStrength <= 0.5f)
{
signalWarningText.Text = TextManager.Get(signalStrength <= 0.0f ? "SonarNoSignal" : "SonarSignalWeak");
signalWarningText.Color = signalStrength <= 0.0f ? Color.Red : Color.Orange;
signalWarningText.Visible = true;
}
else
{
signalWarningText.Visible = false;
}
if (GameMain.GameSession == null) return;
DrawMarker(spriteBatch,
GameMain.GameSession.StartLocation.Name,
(Level.Loaded.StartPosition - transducerCenter), displayScale, center, (rect.Width * 0.5f));
DrawMarker(spriteBatch,
GameMain.GameSession.EndLocation.Name,
(Level.Loaded.EndPosition - transducerCenter), displayScale, center, (rect.Width * 0.5f));
foreach (AITarget aiTarget in AITarget.List)
{
if (!aiTarget.Enabled) continue;
if (string.IsNullOrEmpty(aiTarget.SonarLabel) || aiTarget.SoundRange <= 0.0f) continue;
if (Vector2.DistanceSquared(aiTarget.WorldPosition, transducerCenter) < aiTarget.SoundRange * aiTarget.SoundRange)
{
DrawMarker(spriteBatch,
aiTarget.SonarLabel,
aiTarget.WorldPosition - transducerCenter, displayScale, center, (rect.Width * 0.47f));
}
}
if (GameMain.GameSession.Mission != null)
{
var mission = GameMain.GameSession.Mission;
if (!string.IsNullOrWhiteSpace(mission.SonarLabel) && mission.SonarPosition != Vector2.Zero)
{
DrawMarker(spriteBatch,
mission.SonarLabel,
mission.SonarPosition - transducerCenter, displayScale, center, (rect.Width * 0.47f));
}
}
foreach (Submarine sub in Submarine.Loaded)
{
if (!sub.ShowSonarMarker) { continue; }
if (UseTransducers ?
connectedTransducers.Any(t => sub == t.Transducer.Item.Submarine || sub.DockedTo.Contains(t.Transducer.Item.Submarine)) :
(sub == item.Submarine || sub.DockedTo.Contains(item.Submarine)))
{
continue;
}
if (sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
DrawMarker(spriteBatch, sub.Name, sub.WorldPosition - transducerCenter, displayScale, center, (rect.Width * 0.45f));
}
if (GameMain.DebugDraw)
{
var steering = item.GetComponent<Steering>();
steering?.DebugDrawHUD(spriteBatch, transducerCenter, displayScale, DisplayRadius, center);
}
}
private void DrawOwnSubmarineBorders(SpriteBatch spriteBatch, Vector2 transducerCenter, float signalStrength)
{
float simScale = displayScale * Physics.DisplayToSimRation * zoom;
foreach (Submarine submarine in Submarine.Loaded)
{
if (UseTransducers ?
!connectedTransducers.Any(t => submarine == t.Transducer.Item.Submarine || submarine.DockedTo.Contains(t.Transducer.Item.Submarine)) :
submarine != item.Submarine && !submarine.DockedTo.Contains(item.Submarine)) continue;
if (submarine.HullVertices == null) continue;
Vector2 offset = ConvertUnits.ToSimUnits(submarine.WorldPosition - transducerCenter);
for (int i = 0; i < submarine.HullVertices.Count; i++)
{
Vector2 start = (submarine.HullVertices[i] + offset) * simScale;
start.Y = -start.Y;
Vector2 end = (submarine.HullVertices[(i + 1) % submarine.HullVertices.Count] + offset) * simScale;
end.Y = -end.Y;
DrawLine(spriteBatch, start, end, Color.LightBlue * signalStrength * 0.5f, width: 3);
}
}
}
private void DrawLine(SpriteBatch spriteBatch, Vector2 start, Vector2 end, Color color, int width)
{
bool startOutside = start.LengthSquared() > DisplayRadius * DisplayRadius;
bool endOutside = end.LengthSquared() > DisplayRadius * DisplayRadius;
if (startOutside && endOutside)
{
return;
}
else if (startOutside)
{
if (MathUtils.GetLineCircleIntersections(Vector2.Zero, DisplayRadius, end, start, true, out Vector2? intersection1, out Vector2? intersection2) == 1)
{
GUI.DrawLine(spriteBatch, center + intersection1.Value, center + end, color, width: width);
}
}
else if (endOutside)
{
if (MathUtils.GetLineCircleIntersections(Vector2.Zero, DisplayRadius, start, end, true, out Vector2? intersection1, out Vector2? intersection2) == 1)
{
GUI.DrawLine(spriteBatch, center + start, center + intersection1.Value, color, width: width);
}
}
else
{
GUI.DrawLine(spriteBatch, center + start, center + end, color, width: width);
}
}
private void DrawDockingPorts(SpriteBatch spriteBatch, Vector2 transducerCenter, float signalStrength)
{
float scale = displayScale * zoom;
Steering steering = item.GetComponent<Steering>();
if (steering != null && steering.DockingModeEnabled)
{
DisplayOffset =
Vector2.Lerp(DisplayOffset,
(steering.DockingSource.Item.WorldPosition + steering.DockingTarget.Item.WorldPosition) / 2.0f - transducerCenter,
0.1f);
transducerCenter += DisplayOffset;
Vector2 sourcePortDiff = (steering.DockingSource.Item.WorldPosition - transducerCenter) * scale;
Vector2 sourcePortPos = new Vector2(sourcePortDiff.X, -sourcePortDiff.Y);
Vector2 targetPortDiff = (steering.DockingTarget.Item.WorldPosition - transducerCenter) * scale;
Vector2 targetPortPos = new Vector2(targetPortDiff.X, -targetPortDiff.Y);
Vector2 midPos = (sourcePortPos + targetPortPos) / 2.0f;
System.Diagnostics.Debug.Assert(steering.DockingSource.IsHorizontal == steering.DockingTarget.IsHorizontal);
float xDist = Math.Abs(steering.DockingSource.Item.WorldPosition.X - steering.DockingTarget.Item.WorldPosition.X);
float normalizedXDist = xDist / steering.DockingSource.DistanceTolerance.X;
float yDist = Math.Abs(steering.DockingSource.Item.WorldPosition.Y - steering.DockingTarget.Item.WorldPosition.Y);
float normalizedYDist = yDist / steering.DockingSource.DistanceTolerance.Y;
Color xColor = normalizedXDist <= 1.0f ? Color.Lime : Color.Lerp(Color.Orange, Color.Red, normalizedXDist - 1.0f);
Color yColor = normalizedYDist <= 1.0f ? Color.Lime : Color.Lerp(Color.Orange, Color.Red, normalizedYDist - 1.0f);
if (steering.DockingSource.IsHorizontal)
{
if (yDist < steering.DockingSource.DistanceTolerance.Y)
{
DrawLine(spriteBatch, sourcePortPos, new Vector2(targetPortPos.X, sourcePortPos.Y), xColor, width: 3);
}
else
{
DrawLine(spriteBatch, sourcePortPos, new Vector2(midPos.X, sourcePortPos.Y), xColor, width: 3);
DrawLine(spriteBatch, targetPortPos, new Vector2(midPos.X, targetPortPos.Y), xColor, width: 3);
DrawLine(spriteBatch, new Vector2(midPos.X, sourcePortPos.Y), new Vector2(midPos.X, targetPortPos.Y), yColor, width: 3);
}
}
else
{
if (xDist < steering.DockingSource.DistanceTolerance.X)
{
DrawLine(spriteBatch, sourcePortPos, new Vector2(sourcePortPos.X, targetPortPos.Y), yColor, width: 3);
}
else
{
DrawLine(spriteBatch, sourcePortPos, new Vector2(sourcePortPos.X, midPos.Y), yColor, width: 3);
DrawLine(spriteBatch, targetPortPos, new Vector2(targetPortPos.X, midPos.Y), yColor, width: 3);
DrawLine(spriteBatch, new Vector2(sourcePortPos.X, midPos.Y), new Vector2(targetPortPos.X, midPos.Y), xColor, width: 3);
}
}
}
else
{
DisplayOffset = Vector2.Lerp(DisplayOffset, Vector2.Zero, 0.1f);
}
foreach (DockingPort dockingPort in DockingPort.List)
{
if (Level.Loaded != null && dockingPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
Vector2 offset = (dockingPort.Item.WorldPosition - transducerCenter) * scale;
offset.Y = -offset.Y;
if (offset.LengthSquared() > DisplayRadius * DisplayRadius) { continue; }
Vector2 size = dockingPort.Item.Rect.Size.ToVector2() * scale;
if (dockingPort.IsHorizontal)
{
size.X = 0.0f;
}
else
{
size.Y = 0.0f;
}
GUI.DrawLine(spriteBatch, center + offset - size / 2, center + offset + size / 2, Color.LightGreen, width: (int)(zoom));
}
}
private void UpdateDisruptions(Vector2 pingSource, float worldPingRadius, float worldPrevPingRadius)
{
float worldPingRadiusSqr = worldPingRadius * worldPingRadius;
float worldPrevPingRadiusSqr = worldPrevPingRadius * worldPrevPingRadius;
disruptedDirections.Clear();
if (Level.Loaded == null) { return; }
foreach (LevelObject levelObject in Level.Loaded.LevelObjectManager.GetAllObjects(pingSource, range * pingState))
{
if (levelObject.ActivePrefab?.SonarDisruption <= 0.0f) { continue; }
float disruptionStrength = levelObject.ActivePrefab.SonarDisruption;
Vector2 disruptionPos = new Vector2(levelObject.Position.X, levelObject.Position.Y);
float disruptionDist = Vector2.Distance(pingSource, disruptionPos);
disruptedDirections.Add(new Pair<Vector2, float>((disruptionPos - pingSource) / disruptionDist, disruptionStrength));
if (disruptionDist > worldPrevPingRadius && disruptionDist <= worldPingRadius)
{
for (int i = 0; i < disruptionStrength * Level.GridCellSize * 0.02f; i++)
{
var blip = new SonarBlip(disruptionPos + Rand.Vector(Rand.Range(0.0f, Level.GridCellSize * 4 * disruptionStrength)), MathHelper.Lerp(1.0f, 1.5f, disruptionStrength), Rand.Range(1.0f, 2.0f + disruptionStrength));
sonarBlips.Add(blip);
}
}
}
}
private void Ping(Vector2 pingSource, Vector2 transducerPos, float pingRadius, float prevPingRadius, float displayScale, float range, bool passive,
float pingStrength = 1.0f)
{
float prevPingRadiusSqr = prevPingRadius * prevPingRadius;
float pingRadiusSqr = pingRadius * pingRadius;
//inside a hull -> only show the edges of the hull
if (item.CurrentHull != null && DetectSubmarineWalls)
{
CreateBlipsForLine(
new Vector2(item.CurrentHull.WorldRect.X, item.CurrentHull.WorldRect.Y),
new Vector2(item.CurrentHull.WorldRect.Right, item.CurrentHull.WorldRect.Y),
pingSource, transducerPos,
pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive);
CreateBlipsForLine(
new Vector2(item.CurrentHull.WorldRect.X, item.CurrentHull.WorldRect.Y - item.CurrentHull.Rect.Height),
new Vector2(item.CurrentHull.WorldRect.Right, item.CurrentHull.WorldRect.Y - item.CurrentHull.Rect.Height),
pingSource, transducerPos,
pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive);
CreateBlipsForLine(
new Vector2(item.CurrentHull.WorldRect.X, item.CurrentHull.WorldRect.Y),
new Vector2(item.CurrentHull.WorldRect.X, item.CurrentHull.WorldRect.Y - item.CurrentHull.Rect.Height),
pingSource, transducerPos,
pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive);
CreateBlipsForLine(
new Vector2(item.CurrentHull.WorldRect.Right, item.CurrentHull.WorldRect.Y),
new Vector2(item.CurrentHull.WorldRect.Right, item.CurrentHull.WorldRect.Y - item.CurrentHull.Rect.Height),
pingSource, transducerPos,
pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive);
return;
}
foreach (Submarine submarine in Submarine.Loaded)
{
if (submarine.HullVertices == null) continue;
if (!DetectSubmarineWalls)
{
if (UseTransducers)
{
if (connectedTransducers.Any(t => submarine == t.Transducer.Item.Submarine ||
submarine.DockedTo.Contains(t.Transducer.Item.Submarine))) continue;
}
else
{
if (item.Submarine == submarine) continue;
if (item.Submarine != null && item.Submarine.DockedTo.Contains(submarine)) continue;
}
}
for (int i = 0; i < submarine.HullVertices.Count; i++)
{
Vector2 start = ConvertUnits.ToDisplayUnits(submarine.HullVertices[i]);
Vector2 end = ConvertUnits.ToDisplayUnits(submarine.HullVertices[(i + 1) % submarine.HullVertices.Count]);
if (item.Submarine == submarine)
{
start += Rand.Vector(500.0f);
end += Rand.Vector(500.0f);
}
CreateBlipsForLine(
start + submarine.WorldPosition,
end + submarine.WorldPosition,
pingSource, transducerPos,
pingRadius, prevPingRadius,
200.0f, 2.0f, range, 1.0f, passive);
}
}
if (Level.Loaded != null && (item.CurrentHull == null || !DetectSubmarineWalls))
{
if (Level.Loaded.Size.Y - pingSource.Y < range)
{
CreateBlipsForLine(
new Vector2(pingSource.X - range, Level.Loaded.Size.Y),
new Vector2(pingSource.X + range, Level.Loaded.Size.Y),
pingSource, transducerPos,
pingRadius, prevPingRadius,
250.0f, 150.0f, range, pingStrength, passive);
}
List<Voronoi2.VoronoiCell> cells = Level.Loaded.GetCells(pingSource, 7);
foreach (Voronoi2.VoronoiCell cell in cells)
{
foreach (Voronoi2.GraphEdge edge in cell.Edges)
{
if (!edge.IsSolid) continue;
float cellDot = Vector2.Dot(cell.Center - pingSource, (edge.Center + cell.Translation) - cell.Center);
if (cellDot > 0) continue;
float facingDot = Vector2.Dot(
Vector2.Normalize(edge.Point1 - edge.Point2),
Vector2.Normalize(cell.Center - pingSource));
CreateBlipsForLine(
edge.Point1 + cell.Translation,
edge.Point2 + cell.Translation,
pingSource, transducerPos,
pingRadius, prevPingRadius,
350.0f, 3.0f * (Math.Abs(facingDot) + 1.0f), range, pingStrength, passive);
}
}
foreach (RuinGeneration.Ruin ruin in Level.Loaded.Ruins)
{
if (!MathUtils.CircleIntersectsRectangle(pingSource, range, ruin.Area)) continue;
foreach (var ruinShape in ruin.RuinShapes)
{
foreach (RuinGeneration.Line wall in ruinShape.Walls)
{
float cellDot = Vector2.Dot(
Vector2.Normalize(ruinShape.Center - pingSource),
Vector2.Normalize((wall.A + wall.B) / 2.0f - ruinShape.Center));
if (cellDot > 0) continue;
CreateBlipsForLine(
wall.A, wall.B,
pingSource, transducerPos,
pingRadius, prevPingRadius,
100.0f, 1000.0f, range, pingStrength, passive);
}
}
}
}
foreach (Item item in Item.ItemList)
{
if (item.CurrentHull == null && item.Prefab.SonarSize > 0.0f)
{
float pointDist = ((item.WorldPosition - pingSource) * displayScale).LengthSquared();
if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr)
{
var blip = new SonarBlip(
item.WorldPosition + Rand.Vector(item.Prefab.SonarSize),
MathHelper.Clamp(item.Prefab.SonarSize, 0.1f, pingStrength),
MathHelper.Clamp(item.Prefab.SonarSize * 0.1f, 0.1f, 10.0f));
if (!passive && !CheckBlipVisibility(blip, transducerPos)) continue;
sonarBlips.Add(blip);
}
}
}
foreach (Character c in Character.CharacterList)
{
if (c.AnimController.CurrentHull != null || !c.Enabled) continue;
if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) continue;
foreach (Limb limb in c.AnimController.Limbs)
{
if (!limb.body.Enabled) { continue; }
float pointDist = ((limb.WorldPosition - pingSource) * displayScale).LengthSquared();
if (limb.SimPosition == Vector2.Zero || pointDist > DisplayRadius * DisplayRadius) continue;
if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr)
{
var blip = new SonarBlip(
limb.WorldPosition + Rand.Vector(limb.Mass / 10.0f),
MathHelper.Clamp(limb.Mass, 0.1f, pingStrength),
MathHelper.Clamp(limb.Mass * 0.1f, 0.1f, 2.0f));
if (!passive && !CheckBlipVisibility(blip, transducerPos)) continue;
sonarBlips.Add(blip);
}
}
}
}
private void CreateBlipsForLine(Vector2 point1, Vector2 point2, Vector2 pingSource, Vector2 transducerPos, float pingRadius, float prevPingRadius,
float lineStep, float zStep, float range, float pingStrength, bool passive)
{
lineStep /= zoom;
zStep /= zoom;
range *= displayScale;
float length = (point1 - point2).Length();
Vector2 lineDir = (point2 - point1) / length;
for (float x = 0; x < length; x += lineStep * Rand.Range(0.8f, 1.2f))
{
Vector2 point = point1 + lineDir * x;
//ignore if outside the display
Vector2 transducerDiff = point - transducerPos;
Vector2 transducerDisplayDiff = transducerDiff * displayScale;
if (transducerDisplayDiff.LengthSquared() > DisplayRadius * DisplayRadius) continue;
//ignore if the point is not within the ping
Vector2 pointDiff = point - pingSource;
Vector2 displayPointDiff = pointDiff * displayScale;
float displayPointDistSqr = displayPointDiff.LengthSquared();
if (displayPointDistSqr < prevPingRadius * prevPingRadius || displayPointDistSqr > pingRadius * pingRadius) continue;
//ignore if direction is disrupted
float transducerDist = transducerDiff.Length();
Vector2 pingDirection = transducerDiff / transducerDist;
bool disrupted = false;
foreach (Pair<Vector2, float> disruptDir in disruptedDirections)
{
float dot = Vector2.Dot(pingDirection, disruptDir.First);
if (dot > 1.0f - disruptDir.Second)
{
disrupted = true;
break;
}
}
if (disrupted) continue;
float displayPointDist = (float)Math.Sqrt(displayPointDistSqr);
float alpha = pingStrength * Rand.Range(1.5f, 2.0f);
for (float z = 0; z < DisplayRadius - transducerDist * displayScale; z += zStep)
{
Vector2 pos = point + Rand.Vector(150.0f / zoom) + pingDirection * z / displayScale;
float fadeTimer = alpha * (1.0f - displayPointDist / range);
int minDist = (int)(200 / zoom);
sonarBlips.RemoveAll(b => b.FadeTimer < fadeTimer && Math.Abs(pos.X - b.Position.X) < minDist && Math.Abs(pos.Y - b.Position.Y) < minDist);
var blip = new SonarBlip(pos, fadeTimer, 1.0f + ((displayPointDist + z) / DisplayRadius));
if (!passive && !CheckBlipVisibility(blip, transducerPos)) continue;
sonarBlips.Add(blip);
zStep += 0.5f / zoom;
if (z == 0)
{
alpha = Math.Min(alpha - 0.5f, 1.5f);
}
else
{
alpha -= 0.1f;
}
if (alpha < 0) break;
}
}
}
private bool CheckBlipVisibility(SonarBlip blip, Vector2 transducerPos)
{
Vector2 pos = (blip.Position - transducerPos) * displayScale * zoom;
pos.Y = -pos.Y;
float posDistSqr = pos.LengthSquared();
if (posDistSqr > DisplayRadius * DisplayRadius)
{
blip.FadeTimer = 0.0f;
return false;
}
Vector2 dir = pos / (float)Math.Sqrt(posDistSqr);
if (isLastPingDirectional)
{
if (Vector2.Dot(lastPingDirection, dir) < DirectionalPingDotProduct)
{
blip.FadeTimer = 0.0f;
return false;
}
}
return true;
}
private void DrawBlip(SpriteBatch spriteBatch, SonarBlip blip, Vector2 transducerPos, Vector2 center, float strength)
{
strength = MathHelper.Clamp(strength, 0.0f, 1.0f);
float distort = 1.0f - item.Condition / item.MaxCondition;
Vector2 pos = (blip.Position - transducerPos) * displayScale * zoom;
pos.Y = -pos.Y;
if (Rand.Range(0.5f, 2.0f) < distort) pos.X = -pos.X;
if (Rand.Range(0.5f, 2.0f) < distort) pos.Y = -pos.Y;
float posDistSqr = pos.LengthSquared();
if (posDistSqr > DisplayRadius * DisplayRadius)
{
blip.FadeTimer = 0.0f;
return;
}
if (sonarBlip == null)
{
GUI.DrawRectangle(spriteBatch, center + pos, Vector2.One * 4, Color.Magenta, true);
return;
}
Vector2 dir = pos / (float)Math.Sqrt(posDistSqr);
Vector2 normal = new Vector2(dir.Y, -dir.X);
float scale = (strength + 3.0f) * blip.Scale * zoomSqrt;
Color color = ToolBox.GradientLerp(strength, blipColorGradient);
sonarBlip.Draw(spriteBatch, center + pos, color, sonarBlip.Origin, blip.Rotation ?? MathUtils.VectorToAngle(pos),
blip.Size * scale * 0.04f, SpriteEffects.None, 0);
pos += Rand.Range(0.0f, 1.0f) * dir + Rand.Range(-scale, scale) * normal;
sonarBlip.Draw(spriteBatch, center + pos, color * 0.5f, sonarBlip.Origin, 0, scale * 0.08f, SpriteEffects.None, 0);
}
private void DrawMarker(SpriteBatch spriteBatch, string label, Vector2 position, float scale, Vector2 center, float radius)
{
float dist = position.Length();
position *= zoom;
position *= scale;
position.Y = -position.Y;
float textAlpha = MathHelper.Clamp(1.5f - dist / 50000.0f, 0.5f, 1.0f);
Vector2 dir = Vector2.Normalize(position);
Vector2 markerPos = (dist * zoom * scale > radius) ? dir * radius : position;
markerPos += center;
markerPos.X = (int)markerPos.X;
markerPos.Y = (int)markerPos.Y;
float alpha = 1.0f;
if (dist * scale < radius)
{
float normalizedDist = dist * scale / radius;
alpha = Math.Max(normalizedDist - 0.4f, 0.0f);
float mouseDist = Vector2.Distance(PlayerInput.MousePosition, markerPos);
float hoverThreshold = 150.0f;
if (mouseDist < hoverThreshold)
{
alpha += (hoverThreshold - mouseDist) / hoverThreshold;
}
}
if (!GuiFrame.Children.First().Rect.Contains(markerPos))
{
if (MathUtils.GetLineRectangleIntersection(center, markerPos, GuiFrame.Children.First().Rect, out Vector2 intersection))
{
markerPos = intersection;
}
}
GUI.DrawRectangle(spriteBatch, new Rectangle((int)markerPos.X - 3, (int)markerPos.Y - 3, 6, 6), Color.Red, thickness: 2);
if (alpha <= 0.0f) { return; }
string wrappedLabel = ToolBox.WrapText(label, 150, GUI.SmallFont);
wrappedLabel += "\n" + ((int)(dist * Physics.DisplayToRealWorldRatio) + " m");
Vector2 labelPos = markerPos;
Vector2 textSize = GUI.SmallFont.MeasureString(wrappedLabel);
//flip the text to left side when the marker is on the left side or goes outside the right edge of the interface
if ((dir.X < 0.0f || labelPos.X + textSize.X + 10 > GuiFrame.Rect.X) && labelPos.X - textSize.X > 0) labelPos.X -= textSize.X + 10;
GUI.DrawString(spriteBatch,
new Vector2(labelPos.X + 10, labelPos.Y),
wrappedLabel,
Color.LightBlue * textAlpha * alpha, Color.Black * textAlpha * 0.8f * alpha,
2, GUI.SmallFont);
}
public void ClientWrite(Lidgren.Network.NetBuffer msg, object[] extraData = null)
{
msg.Write(IsActive);
if (IsActive)
{
msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8);
msg.Write(useDirectionalPing);
if (useDirectionalPing)
{
msg.WriteRangedSingle(directionalSlider.BarScroll, 0.0f, 1.0f, 8);
}
}
}
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime)
{
long msgStartPos = msg.Position;
bool isActive = msg.ReadBoolean();
float zoomT = 1.0f;
bool directionalPing = useDirectionalPing;
float directionT = 0.0f;
if (isActive)
{
zoomT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
directionalPing = msg.ReadBoolean();
if (directionalPing)
{
directionT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
}
}
if (correctionTimer > 0.0f)
{
int msgLength = (int)(msg.Position - msgStartPos);
msg.Position = msgStartPos;
StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime);
return;
}
IsActive = isActive;
if (isActive)
{
activeTickBox.Selected = true;
zoomSlider.BarScroll = zoomT;
zoom = MathHelper.Lerp(MinZoom, MaxZoom, zoomT);
if (directionalPing)
{
directionalSlider.BarScroll = directionT;
float pingAngle = MathHelper.Lerp(0.0f, MathHelper.TwoPi, directionalSlider.BarScroll);
pingDirection = new Vector2((float)Math.Cos(pingAngle), (float)Math.Sin(pingAngle));
}
useDirectionalPing = directionalTickBox.Selected = directionalPing;
}
else
{
passiveTickBox.Selected = true;
}
}
}
class SonarBlip
{
public float FadeTimer;
public Vector2 Position;
public float Scale;
public Vector2 Velocity;
public float? Rotation;
public Vector2 Size;
public SonarBlip(Vector2 pos, float fadeTimer, float scale)
{
Position = pos;
FadeTimer = Math.Max(fadeTimer, 0.0f);
Scale = scale;
Size = new Vector2(0.5f, 1.0f);
}
}
}