using Barotrauma.Networking; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; namespace Barotrauma.Items.Components { partial class Sonar : Powered, IServerSerializable, IClientSerializable { public const float DefaultSonarRange = 10000.0f; class ConnectedTransducer { public readonly SonarTransducer Transducer; public float SignalStrength; public float DisconnectTimer; public ConnectedTransducer(SonarTransducer transducer, float signalStrength, float disconnectTimer) { Transducer = transducer; SignalStrength = signalStrength; DisconnectTimer = disconnectTimer; } } private const float DirectionalPingSector = 30.0f; private static readonly float DirectionalPingDotProduct; static Sonar() { DirectionalPingDotProduct = (float)Math.Cos(MathHelper.ToRadians(DirectionalPingSector) * 0.5f); } private float range; private float pingState; private const float MinZoom = 1.0f, MaxZoom = 4.0f; private float zoom = 1.0f; private bool useDirectionalPing = false; private Vector2 lastPingDirection = new Vector2(1.0f, 0.0f); private Vector2 pingDirection = new Vector2(1.0f, 0.0f); //was the last ping sent with directional pinging private bool isLastPingDirectional; private readonly Sprite pingCircle, directionalPingCircle, screenOverlay, screenBackground; private readonly Sprite sonarBlip; private bool aiPingCheckPending; //the float value is a timer used for disconnecting the transducer if no signal is received from it for 1 second private List connectedTransducers; public IEnumerable ConnectedTransducers { get { return connectedTransducers.Select(t => t.Transducer); } } [Serialize(DefaultSonarRange, false)] public float Range { get { return range; } set { range = MathHelper.Clamp(value, 0.0f, 100000.0f); } } [Serialize(false, false)] public bool DetectSubmarineWalls { get; set; } [Serialize(false, false), Editable(ToolTip = "Does the sonar have to be connected to external transducers to work.")] public bool UseTransducers { get; set; } public float Zoom { get { return zoom; } } public override bool IsActive { get { return base.IsActive; } set { base.IsActive = value; if (!value && item.CurrentHull != null) { item.CurrentHull.AiTarget.SectorDegrees = 360.0f; } #if CLIENT if (activeTickBox != null) activeTickBox.Selected = value; if (passiveTickBox != null) passiveTickBox.Selected = !value; #endif } } public Sonar(Item item, XElement element) : base(item, element) { connectedTransducers = new List(); foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "pingcircle": pingCircle = new Sprite(subElement); break; case "directionalpingcircle": directionalPingCircle = new Sprite(subElement); break; case "screenoverlay": screenOverlay = new Sprite(subElement); break; case "screenbackground": screenBackground = new Sprite(subElement); break; case "blip": sonarBlip = new Sprite(subElement); break; } } IsActive = false; InitProjSpecific(element); } partial void InitProjSpecific(XElement element); public override void Update(float deltaTime, Camera cam) { currPowerConsumption = powerConsumption; UpdateOnActiveEffects(deltaTime); if (UseTransducers) { foreach (ConnectedTransducer transducer in connectedTransducers) { transducer.DisconnectTimer -= deltaTime; } connectedTransducers.RemoveAll(t => t.DisconnectTimer <= 0.0f); } if ((voltage >= minVoltage || powerConsumption <= 0.0f) && (!UseTransducers || connectedTransducers.Count > 0)) { pingState = pingState + deltaTime * 0.5f; if (pingState > 1.0f) { if (item.CurrentHull != null) { item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * pingState / zoom, item.CurrentHull.AiTarget.SoundRange); item.CurrentHull.AiTarget.SectorDegrees = isLastPingDirectional ? DirectionalPingSector : 360.0f; item.CurrentHull.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y); } if (item.AiTarget != null) { item.AiTarget.SoundRange = Math.Max(Range * pingState / zoom, item.AiTarget.SoundRange); item.AiTarget.SectorDegrees = isLastPingDirectional ? DirectionalPingSector : 360.0f; item.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y); } aiPingCheckPending = true; isLastPingDirectional = useDirectionalPing; lastPingDirection = pingDirection; item.Use(deltaTime); pingState = 0.0f; } } else { if (item.CurrentHull != null) { item.CurrentHull.AiTarget.SectorDegrees = 360.0f; } aiPingCheckPending = false; pingState = 0.0f; } Voltage -= deltaTime; } public override bool Use(float deltaTime, Character character = null) { return pingState > 1.0f; } protected override void RemoveComponentSpecific() { sonarBlip?.Remove(); pingCircle?.Remove(); directionalPingCircle?.Remove(); screenOverlay?.Remove(); screenBackground?.Remove(); } public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) { if (!IsActive || !aiPingCheckPending) return false; Dictionary> targetGroups = new Dictionary>(); foreach (Character c in Character.CharacterList) { if (c.AnimController.CurrentHull != null || !c.Enabled) continue; if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) continue; if (Vector2.DistanceSquared(c.WorldPosition, item.WorldPosition) > range * range) continue; string directionName = GetDirectionName(c.WorldPosition - item.WorldPosition); if (!targetGroups.ContainsKey(directionName)) { targetGroups.Add(directionName, new List()); } targetGroups[directionName].Add(c); } foreach (KeyValuePair> targetGroup in targetGroups) { string dialogTag = "DialogSonarTarget"; if (targetGroup.Value.Count > 1) { dialogTag = "DialogSonarTargetMultiple"; } else if (targetGroup.Value[0].Mass > 100.0f) { dialogTag = "DialogSonarTargetLarge"; } character.Speak(TextManager.Get(dialogTag).Replace("[direction]", targetGroup.Key).Replace("[count]", targetGroup.Value.Count.ToString()), null, 0, "sonartarget" + targetGroup.Value[0].ID, 30); //prevent the character from reporting other targets in the group for (int i = 1; i < targetGroup.Value.Count; i++) { character.DisableLine("sonartarget" + targetGroup.Value[i].ID); } } return true; } private string GetDirectionName(Vector2 dir) { float angle = MathUtils.WrapAngleTwoPi((float)-Math.Atan2(dir.Y, dir.X) + MathHelper.PiOver2); int clockDir = (int)Math.Round((angle / MathHelper.TwoPi) * 12); if (clockDir == 0) clockDir = 12; return TextManager.Get("SubDirOClock").Replace("[dir]", clockDir.ToString()); } private Vector2 GetTransducerCenter() { if (!UseTransducers || connectedTransducers.Count == 0) return Vector2.Zero; Vector2 transducerPosSum = Vector2.Zero; foreach (ConnectedTransducer transducer in connectedTransducers) { transducerPosSum += transducer.Transducer.Item.WorldPosition; } return transducerPosSum / connectedTransducers.Count; } public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1.0f) { base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength); if (connection.Name == "transducer_in") { var transducer = source.GetComponent(); if (transducer == null) return; var connectedTransducer = connectedTransducers.Find(t => t.Transducer == transducer); if (connectedTransducer == null) { connectedTransducers.Add(new ConnectedTransducer(transducer, signalStrength, 1.0f)); } else { connectedTransducer.SignalStrength = signalStrength; connectedTransducer.DisconnectTimer = 1.0f; } } } public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Client c) { bool isActive = msg.ReadBoolean(); bool directionalPing = useDirectionalPing; float zoomT = zoom, pingDirectionT = 0.0f; if (isActive) { zoomT = msg.ReadRangedSingle(0.0f, 1.0f, 8); directionalPing = msg.ReadBoolean(); if (directionalPing) { pingDirectionT = msg.ReadRangedSingle(0.0f, 1.0f, 8); } } if (!item.CanClientAccess(c)) return; IsActive = isActive; //TODO: cleanup #if CLIENT activeTickBox.Selected = IsActive; #endif if (isActive) { zoom = MathHelper.Lerp(MinZoom, MaxZoom, zoomT); useDirectionalPing = directionalPing; if (useDirectionalPing) { float pingAngle = MathHelper.Lerp(0.0f, MathHelper.TwoPi, pingDirectionT); pingDirection = new Vector2((float)Math.Cos(pingAngle), (float)Math.Sin(pingAngle)); } #if CLIENT zoomSlider.BarScroll = zoomT; directionalTickBox.Selected = useDirectionalPing; directionalSlider.BarScroll = pingDirectionT; #endif } #if SERVER item.CreateServerEvent(this); #endif } public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null) { msg.Write(IsActive); if (IsActive) { msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8); msg.Write(useDirectionalPing); if (useDirectionalPing) { float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection)); msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8); } } } } }