Conflicts: Subsurface/Source/Characters/AI/EnemyAIController.cs Subsurface/Source/DebugConsole.cs Subsurface/Source/Items/Components/Machines/Radar.cs Subsurface/Source/Items/Item.cs
544 lines
20 KiB
C#
544 lines
20 KiB
C#
using Barotrauma.Networking;
|
|
using FarseerPhysics;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Xml.Linq;
|
|
using Voronoi2;
|
|
|
|
namespace Barotrauma.Items.Components
|
|
{
|
|
class Radar : Powered, IServerSerializable, IClientSerializable
|
|
{
|
|
private float range;
|
|
|
|
private float pingState;
|
|
|
|
private readonly Sprite pingCircle, screenOverlay;
|
|
|
|
private GUITickBox isActiveTickBox;
|
|
|
|
private List<RadarBlip> radarBlips;
|
|
private float prevPingRadius;
|
|
|
|
float prevPassivePingRadius;
|
|
|
|
private Vector2 center;
|
|
private float displayRadius;
|
|
private float displayScale;
|
|
|
|
[HasDefaultValue(10000.0f, false)]
|
|
public float Range
|
|
{
|
|
get { return range; }
|
|
set { range = MathHelper.Clamp(value, 0.0f, 100000.0f); }
|
|
}
|
|
|
|
[HasDefaultValue(false, false)]
|
|
public bool DetectSubmarineWalls
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public override bool IsActive
|
|
{
|
|
get
|
|
{
|
|
return base.IsActive;
|
|
}
|
|
|
|
set
|
|
{
|
|
base.IsActive = value;
|
|
if (isActiveTickBox != null) isActiveTickBox.Selected = value;
|
|
}
|
|
}
|
|
|
|
public Radar(Item item, XElement element)
|
|
: base(item, element)
|
|
{
|
|
radarBlips = new List<RadarBlip>();
|
|
|
|
foreach (XElement subElement in element.Elements())
|
|
{
|
|
switch (subElement.Name.ToString().ToLowerInvariant())
|
|
{
|
|
case "pingcircle":
|
|
pingCircle = new Sprite(subElement);
|
|
break;
|
|
case "screenoverlay":
|
|
screenOverlay = new Sprite(subElement);
|
|
break;
|
|
}
|
|
}
|
|
|
|
isActiveTickBox = new GUITickBox(new Rectangle(0, 0, 20, 20), "Active Sonar", Alignment.TopLeft, GuiFrame);
|
|
isActiveTickBox.OnSelected = (GUITickBox box) =>
|
|
{
|
|
if (GameMain.Server != null)
|
|
{
|
|
item.CreateServerEvent(this);
|
|
}
|
|
else if (GameMain.Client != null)
|
|
{
|
|
item.CreateClientEvent(this);
|
|
correctionTimer = CorrectionDelay;
|
|
}
|
|
IsActive = box.Selected;
|
|
|
|
return true;
|
|
};
|
|
|
|
GuiFrame.CanBeFocused = false;
|
|
|
|
IsActive = false;
|
|
}
|
|
|
|
public override void Update(float deltaTime, Camera cam)
|
|
{
|
|
currPowerConsumption = powerConsumption;
|
|
|
|
base.Update(deltaTime, cam);
|
|
|
|
if (voltage >= minVoltage || powerConsumption <= 0.0f)
|
|
{
|
|
pingState = pingState + deltaTime * 0.5f;
|
|
if (pingState > 1.0f)
|
|
{
|
|
if (item.CurrentHull != null) item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * pingState, item.CurrentHull.AiTarget.SoundRange);
|
|
item.Use(deltaTime);
|
|
pingState = 0.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pingState = 0.0f;
|
|
}
|
|
|
|
Voltage -= deltaTime;
|
|
}
|
|
|
|
public override bool Use(float deltaTime, Character character = null)
|
|
{
|
|
return pingState > 1.0f;
|
|
}
|
|
|
|
public override void AddToGUIUpdateList()
|
|
{
|
|
GuiFrame.AddToGUIUpdateList();
|
|
}
|
|
|
|
public override void UpdateHUD(Character character)
|
|
{
|
|
GuiFrame.Update((float)Timing.Step);
|
|
|
|
for (int i = radarBlips.Count - 1; i >= 0; i--)
|
|
{
|
|
radarBlips[i].FadeTimer -= (float)Timing.Step * 0.5f;
|
|
if (radarBlips[i].FadeTimer <= 0.0f) radarBlips.RemoveAt(i);
|
|
}
|
|
|
|
float radius = (GuiFrame.Rect.Height / 2 - 30);
|
|
|
|
if (IsActive)
|
|
{
|
|
float pingRadius = displayRadius * pingState;
|
|
Ping(item.WorldPosition, pingRadius, prevPingRadius, displayScale, range, 2.0f);
|
|
prevPingRadius = pingRadius;
|
|
}
|
|
|
|
float passivePingRadius = (float)Math.Sin(GameMain.Instance.TotalElapsedTime*10);
|
|
if (passivePingRadius > 0.0f)
|
|
{
|
|
foreach (AITarget t in AITarget.List)
|
|
{
|
|
if (t.SoundRange <= 0.0f) continue;
|
|
|
|
if (Vector2.Distance(t.WorldPosition, item.WorldPosition) < t.SoundRange)
|
|
{
|
|
Ping(t.WorldPosition, t.SoundRange * passivePingRadius * 0.2f, t.SoundRange * prevPassivePingRadius * 0.2f, displayScale, t.SoundRange, 0.5f);
|
|
|
|
radarBlips.Add(new RadarBlip(t.WorldPosition, 1.0f));
|
|
}
|
|
}
|
|
}
|
|
prevPassivePingRadius = passivePingRadius;
|
|
|
|
}
|
|
|
|
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
|
|
{
|
|
GuiFrame.Draw(spriteBatch);
|
|
|
|
int radius = GuiFrame.Rect.Height / 2 - 30;
|
|
DrawRadar(spriteBatch, new Rectangle((int)GuiFrame.Center.X - radius, (int)GuiFrame.Center.Y - radius, radius * 2, radius * 2));
|
|
}
|
|
|
|
private void DrawRadar(SpriteBatch spriteBatch, Rectangle rect)
|
|
{
|
|
center = new Vector2(rect.X + rect.Width * 0.5f, rect.Center.Y);
|
|
displayRadius = rect.Width / 2.0f;
|
|
displayScale = displayRadius / range;
|
|
|
|
if (IsActive)
|
|
{
|
|
pingCircle.Draw(spriteBatch, center, Color.White * (1.0f - pingState), 0.0f, (rect.Width / pingCircle.size.X) * pingState);
|
|
}
|
|
|
|
if (item.Submarine != null && !DetectSubmarineWalls)
|
|
{
|
|
float simScale = displayScale * Physics.DisplayToSimRation;
|
|
|
|
foreach (Submarine submarine in Submarine.Loaded)
|
|
{
|
|
if (submarine != item.Submarine && !submarine.DockedTo.Contains(item.Submarine)) continue;
|
|
|
|
Vector2 offset = ConvertUnits.ToSimUnits(submarine.WorldPosition - item.WorldPosition);
|
|
|
|
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;
|
|
|
|
GUI.DrawLine(spriteBatch, center + start, center + end, Color.Green);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (RadarBlip radarBlip in radarBlips)
|
|
{
|
|
DrawBlip(spriteBatch, radarBlip, center, radarBlip.FadeTimer / 2.0f);
|
|
}
|
|
|
|
if (screenOverlay != null)
|
|
{
|
|
screenOverlay.Draw(spriteBatch, center, 0.0f, rect.Width / screenOverlay.size.X);
|
|
}
|
|
|
|
if (GameMain.GameSession == null) return;
|
|
|
|
DrawMarker(spriteBatch,
|
|
GameMain.GameSession.StartLocation.Name,
|
|
(Level.Loaded.StartPosition - item.WorldPosition), displayScale, center, (rect.Width * 0.5f));
|
|
|
|
DrawMarker(spriteBatch,
|
|
GameMain.GameSession.EndLocation.Name,
|
|
(Level.Loaded.EndPosition - item.WorldPosition), displayScale, center, (rect.Width * 0.5f));
|
|
|
|
if (GameMain.GameSession.Mission != null)
|
|
{
|
|
var mission = GameMain.GameSession.Mission;
|
|
|
|
if (!string.IsNullOrWhiteSpace(mission.RadarLabel) && mission.RadarPosition != Vector2.Zero)
|
|
{
|
|
DrawMarker(spriteBatch,
|
|
mission.RadarLabel,
|
|
mission.RadarPosition - item.WorldPosition, displayScale, center, (rect.Width * 0.55f));
|
|
}
|
|
}
|
|
|
|
foreach (Submarine sub in Submarine.Loaded)
|
|
{
|
|
if (!sub.OnRadar) continue;
|
|
if (item.Submarine == sub || sub.DockedTo.Contains(item.Submarine)) continue;
|
|
if (sub.WorldPosition.Y > Level.Loaded.Size.Y) continue;
|
|
|
|
DrawMarker(spriteBatch, sub.Name, sub.WorldPosition - item.WorldPosition, displayScale, center, (rect.Width * 0.45f));
|
|
}
|
|
|
|
if (!GameMain.DebugDraw) return;
|
|
|
|
var steering = item.GetComponent<Steering>();
|
|
if (steering == null || steering.SteeringPath == null) return;
|
|
|
|
Vector2 prevPos = Vector2.Zero;
|
|
|
|
foreach (WayPoint wp in steering.SteeringPath.Nodes)
|
|
{
|
|
Vector2 pos = (wp.Position - item.WorldPosition) * displayScale;
|
|
if (pos.Length() > displayRadius) continue;
|
|
|
|
pos.Y = -pos.Y;
|
|
pos += center;
|
|
|
|
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X - 3 / 2, (int)pos.Y - 3, 6, 6), (steering.SteeringPath.CurrentNode == wp) ? Color.LightGreen : Color.Green, false);
|
|
|
|
if (prevPos != Vector2.Zero)
|
|
{
|
|
GUI.DrawLine(spriteBatch, pos, prevPos, Color.Green);
|
|
}
|
|
|
|
prevPos = pos;
|
|
}
|
|
}
|
|
|
|
private void Ping(Vector2 pingSource, float pingRadius, float prevPingRadius, float displayScale, float range, float pingStrength = 1.0f)
|
|
{
|
|
foreach (Submarine submarine in Submarine.Loaded)
|
|
{
|
|
if (item.Submarine == submarine && !DetectSubmarineWalls) continue;
|
|
if (item.Submarine != null && item.Submarine.DockedTo.Contains(submarine)) continue;
|
|
if (submarine.HullVertices == null) 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,
|
|
pingRadius, prevPingRadius,
|
|
200.0f, 2.0f, range, 1.0f);
|
|
}
|
|
}
|
|
|
|
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),
|
|
pingRadius, prevPingRadius,
|
|
250.0f, 150.0f, range, pingStrength);
|
|
}
|
|
|
|
List<VoronoiCell> cells = Level.Loaded.GetCells(pingSource, 7);
|
|
foreach (VoronoiCell cell in cells)
|
|
{
|
|
foreach (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,
|
|
pingRadius, prevPingRadius,
|
|
350.0f, 3.0f * (Math.Abs(facingDot) + 1.0f), range, pingStrength);
|
|
}
|
|
}
|
|
|
|
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,
|
|
pingRadius, prevPingRadius,
|
|
100.0f, 1000.0f, range, pingStrength);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
float pointDist = (limb.WorldPosition - pingSource).Length() * displayScale;
|
|
|
|
if (limb.SimPosition == Vector2.Zero || pointDist > displayRadius) continue;
|
|
|
|
if (pointDist > prevPingRadius && pointDist < pingRadius)
|
|
{
|
|
for (int i = 0; i <= limb.Mass / 100.0f; i++)
|
|
{
|
|
var blip = new RadarBlip(limb.WorldPosition + Rand.Vector(limb.Mass / 10.0f), MathHelper.Clamp(limb.Mass, 0.1f, pingStrength));
|
|
radarBlips.Add(blip);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CreateBlipsForLine(Vector2 point1, Vector2 point2, float pingRadius, float prevPingRadius,
|
|
float lineStep, float zStep, float range, float pingStrength)
|
|
{
|
|
float length = (point1 - point2).Length();
|
|
|
|
Vector2 lineDir = (point2 - point1) / length;
|
|
|
|
range *= displayScale;
|
|
|
|
for (float x = 0; x < length; x += lineStep*Rand.Range(0.8f,1.2f))
|
|
{
|
|
Vector2 point = point1 + lineDir * x;
|
|
//point += cell.Translation;
|
|
|
|
float pointDist = Vector2.Distance(item.WorldPosition, point) * displayScale;
|
|
|
|
if (pointDist > displayRadius) continue;
|
|
if (pointDist < prevPingRadius || pointDist > pingRadius) continue;
|
|
|
|
float alpha = pingStrength * Rand.Range(1.5f, 2.0f);
|
|
for (float z = 0; z < displayRadius - pointDist * displayScale; z += zStep)
|
|
{
|
|
var blip = new RadarBlip(
|
|
point + Rand.Vector(150.0f) + Vector2.Normalize(point - item.WorldPosition) * z / displayScale,
|
|
alpha * (1.0f - pointDist/range));
|
|
|
|
radarBlips.Add(blip);
|
|
zStep += 0.5f;
|
|
alpha -= (z == 0) ? 0.5f : 0.1f;
|
|
if (alpha < 0) break;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
private void DrawBlip(SpriteBatch spriteBatch, RadarBlip blip, Vector2 center, float strength)
|
|
{
|
|
strength = MathHelper.Clamp(strength, 0.0f, 1.0f);
|
|
|
|
Color[] colors = new Color[] {
|
|
Color.TransparentBlack,
|
|
new Color(0, 50, 160),
|
|
new Color(0, 133, 166),
|
|
new Color(2, 159, 30),
|
|
new Color(255, 255, 255) };
|
|
|
|
float scaledT = strength * (colors.Length - 1);
|
|
Color color = Color.Lerp(colors[(int)scaledT], colors[(int)scaledT], (scaledT - (int)scaledT));
|
|
|
|
Vector2 pos = (blip.Position - item.WorldPosition) * displayScale;
|
|
pos.Y = -pos.Y;
|
|
|
|
if (pos.Length() > displayRadius)
|
|
{
|
|
blip.FadeTimer = 0.0f;
|
|
return;
|
|
}
|
|
|
|
/*pos.X = MathUtils.Round(pos.X, 4);
|
|
pos.Y = MathUtils.Round(pos.Y, 2);*/
|
|
|
|
|
|
float posDist = pos.Length();
|
|
Vector2 dir = pos / posDist;
|
|
float distFactor = (posDist / displayRadius);
|
|
|
|
Vector2 normal = new Vector2(dir.Y, -dir.X) * (strength + 1.0f) * distFactor * 5.0f;
|
|
|
|
GUI.DrawLine(spriteBatch, center + pos - normal, center + pos + normal, color * (1.0f - distFactor), 0, 2);
|
|
|
|
pos += Rand.Range(0.0f, 1.0f) * dir + Rand.Range(-1.0f, 1.0f) * normal;
|
|
GUI.DrawLine(spriteBatch, center + pos - normal, center + pos + normal, color * 0.2f, 0, 3);
|
|
}
|
|
|
|
private void DrawMarker(SpriteBatch spriteBatch, string label, Vector2 position, float scale, Vector2 center, float radius)
|
|
{
|
|
//position += Level.Loaded.Position;
|
|
|
|
float dist = position.Length();
|
|
|
|
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*scale>radius) ? dir * radius : position;
|
|
markerPos += center;
|
|
|
|
markerPos.X = (int)markerPos.X;
|
|
markerPos.Y = (int)markerPos.Y;
|
|
|
|
GUI.DrawRectangle(spriteBatch, new Rectangle((int)markerPos.X, (int)markerPos.Y, 5, 5), Color.LightGreen * textAlpha);
|
|
|
|
if (dir.X < 0.0f) markerPos.X -= GUI.SmallFont.MeasureString(label).X+10;
|
|
|
|
string wrappedLabel = ToolBox.WrapText(label, 150, GUI.SmallFont);
|
|
|
|
wrappedLabel += "\n"+((int)(dist * Physics.DisplayToRealWorldRatio) + " m");
|
|
|
|
GUI.DrawString(spriteBatch,
|
|
new Vector2(markerPos.X + 10, markerPos.Y),
|
|
wrappedLabel,
|
|
Color.LightGreen * textAlpha, Color.Black * textAlpha * 0.5f,
|
|
2, GUI.SmallFont);
|
|
}
|
|
|
|
protected override void RemoveComponentSpecific()
|
|
{
|
|
if (pingCircle!=null) pingCircle.Remove();
|
|
if (screenOverlay != null) screenOverlay.Remove();
|
|
}
|
|
|
|
public void ClientWrite(Lidgren.Network.NetBuffer msg, object[] extraData = null)
|
|
{
|
|
msg.Write(IsActive);
|
|
}
|
|
|
|
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c)
|
|
{
|
|
bool isActive = msg.ReadBoolean();
|
|
|
|
if (!item.CanClientAccess(c)) return;
|
|
|
|
IsActive = isActive;
|
|
isActiveTickBox.Selected = IsActive;
|
|
|
|
item.CreateServerEvent(this);
|
|
}
|
|
|
|
public void ServerWrite(Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c, object[] extraData = null)
|
|
{
|
|
msg.Write(IsActive);
|
|
}
|
|
|
|
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime)
|
|
{
|
|
if (correctionTimer > 0.0f)
|
|
{
|
|
StartDelayedCorrection(type, msg.ExtractBits(1), sendingTime);
|
|
return;
|
|
}
|
|
|
|
IsActive = msg.ReadBoolean();
|
|
isActiveTickBox.Selected = IsActive;
|
|
}
|
|
}
|
|
|
|
class RadarBlip
|
|
{
|
|
public float FadeTimer;
|
|
public Vector2 Position;
|
|
|
|
public RadarBlip(Vector2 pos, float fadeTimer)
|
|
{
|
|
Position = pos;
|
|
FadeTimer = Math.Max(fadeTimer, 0.0f);
|
|
}
|
|
}
|
|
}
|