Server LuaCsForBarotrauma
Sonar.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 
8 {
10  {
11  public enum Mode
12  {
13  Active,
14  Passive
15  };
16 
17  public const float DefaultSonarRange = 10000.0f;
18 
19  public const float PassivePowerConsumption = 0.1f;
20 
21  class ConnectedTransducer
22  {
23  public readonly SonarTransducer Transducer;
24  public float SignalStrength;
25  public float DisconnectTimer;
26 
27  public ConnectedTransducer(SonarTransducer transducer, float signalStrength, float disconnectTimer)
28  {
29  Transducer = transducer;
30  SignalStrength = signalStrength;
31  DisconnectTimer = disconnectTimer;
32  }
33  }
34 
35  private const float DirectionalPingSector = 30.0f;
36  private static readonly float DirectionalPingDotProduct;
37 
38  static Sonar()
39  {
40  DirectionalPingDotProduct = (float)Math.Cos(MathHelper.ToRadians(DirectionalPingSector) * 0.5f);
41  }
42 
43  private float range;
44 
45  private const float PingFrequency = 0.5f;
46 
47  private Mode currentMode = Mode.Passive;
48 
49  private class ActivePing
50  {
51  public float State;
52  public bool IsDirectional;
53  public Vector2 Direction;
54  public float PrevPingRadius;
55  }
56  // rotating list of currently active pings
57  private ActivePing[] activePings = new ActivePing[8];
58  // total number of currently active pings, range [0, activePings.Length[
59  private int activePingsCount;
60  // currently active ping index on the above list
61  private int currentPingIndex = -1;
62 
63  private const float MinZoom = 1.0f, MaxZoom = 4.0f;
64  private float zoom = 1.0f;
65 
67  public bool UseDirectionalPing => useDirectionalPing;
68  private bool useDirectionalPing = false;
69  private Vector2 pingDirection = new Vector2(1.0f, 0.0f);
70 
71  private bool aiPingCheckPending;
72 
73  //the float value is a timer used for disconnecting the transducer if no signal is received from it for 1 second
74  private readonly List<ConnectedTransducer> connectedTransducers;
75 
76  public IEnumerable<SonarTransducer> ConnectedTransducers
77  {
78  get { return connectedTransducers.Select(t => t.Transducer); }
79  }
80 
81  [Serialize(DefaultSonarRange, IsPropertySaveable.No, description: "The maximum range of the sonar.")]
82  public float Range
83  {
84  get { return range; }
85  set
86  {
87  range = MathHelper.Clamp(value, 0.0f, 100000.0f);
88  if (item?.AiTarget != null && item.AiTarget.MaxSoundRange <= 0)
89  {
90  item.AiTarget.MaxSoundRange = range;
91  }
92  }
93  }
94 
95  [Serialize(false, IsPropertySaveable.No, description: "Should the sonar display the walls of the submarine it is inside.")]
97  {
98  get;
99  set;
100  }
101 
102  [Editable, Serialize(false, IsPropertySaveable.No, description: "Does the sonar have to be connected to external transducers to work.")]
103  public bool UseTransducers
104  {
105  get;
106  set;
107  }
108 
109  [Editable, Serialize(false, IsPropertySaveable.No, description: "Should the sonar view be centered on the transducers or the submarine's center of mass. Only has an effect if UseTransducers is enabled.")]
111  {
112  get;
113  set;
114  }
115 
116  private bool hasMineralScanner;
117 
118  [Editable, Serialize(false, IsPropertySaveable.No, description: "Does the sonar have mineral scanning mode. ")]
119  public bool HasMineralScanner
120  {
121  get => hasMineralScanner;
122  set
123  {
124 #if CLIENT
125  if (controlContainer != null && !hasMineralScanner && value)
126  {
127  AddMineralScannerSwitchToGUI();
128  }
129 #endif
130  hasMineralScanner = value;
131  }
132  }
133 
134  [Serialize(true, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
135  public bool UseMineralScanner { get; set; }
136 
137  public float Zoom
138  {
139  get { return zoom; }
140  set
141  {
142  zoom = MathHelper.Clamp(value, MinZoom, MaxZoom);
143 #if CLIENT
144  zoomSlider.BarScroll = MathUtils.InverseLerp(MinZoom, MaxZoom, zoom);
145 #endif
146  }
147  }
148 
150  {
151  get => currentMode;
152  set
153  {
154  bool changed = currentMode != value;
155  currentMode = value;
156 #if CLIENT
157  if (changed) { prevPassivePingRadius = float.MaxValue; }
158  UpdateGUIElements();
159 #endif
160  }
161  }
162 
163  public Sonar(Item item, ContentXElement element)
164  : base(item, element)
165  {
166  connectedTransducers = new List<ConnectedTransducer>();
167  IsActive = true;
168  InitProjSpecific(element);
169  CurrentMode = Mode.Passive;
170  }
171 
172  partial void InitProjSpecific(ContentXElement element);
173 
174  public override void Update(float deltaTime, Camera cam)
175  {
176  UpdateOnActiveEffects(deltaTime);
177 
178  if (UseTransducers)
179  {
180  foreach (ConnectedTransducer transducer in connectedTransducers)
181  {
182  transducer.DisconnectTimer -= deltaTime;
183  }
184  connectedTransducers.RemoveAll(t => t.DisconnectTimer <= 0.0f);
185  }
186 
187  for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex)
188  {
189  activePings[pingIndex].State += deltaTime * PingFrequency;
190  }
191 
192  if (currentMode == Mode.Active)
193  {
194  if (HasPower && (!UseTransducers || connectedTransducers.Count > 0))
195  {
196  if (currentPingIndex != -1)
197  {
198  var activePing = activePings[currentPingIndex];
199  if (activePing.State > 1.0f)
200  {
201  aiPingCheckPending = true;
202  currentPingIndex = -1;
203  }
204  }
205  if (currentPingIndex == -1 && activePingsCount < activePings.Length)
206  {
207  currentPingIndex = activePingsCount++;
208  if (activePings[currentPingIndex] == null)
209  {
210  activePings[currentPingIndex] = new ActivePing();
211  }
212  activePings[currentPingIndex].IsDirectional = useDirectionalPing;
213  activePings[currentPingIndex].Direction = pingDirection;
214  activePings[currentPingIndex].State = 0.0f;
215  activePings[currentPingIndex].PrevPingRadius = 0.0f;
216  foreach (AITarget aiTarget in GetAITargets())
217  {
218  aiTarget.SectorDegrees = useDirectionalPing ? DirectionalPingSector : 360.0f;
219  aiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
220  }
221  item.Use(deltaTime);
222  }
223  }
224  else
225  {
226  aiPingCheckPending = false;
227  }
228  }
229 
230  for (var pingIndex = 0; pingIndex < activePingsCount;)
231  {
232  foreach (AITarget aiTarget in GetAITargets())
233  {
234  float range = MathUtils.InverseLerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, Range * activePings[pingIndex].State / zoom);
235  aiTarget.SoundRange = Math.Max(aiTarget.SoundRange, MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range));
236  }
237  if (activePings[pingIndex].State > 1.0f)
238  {
239  var lastIndex = --activePingsCount;
240  var oldActivePing = activePings[pingIndex];
241  activePings[pingIndex] = activePings[lastIndex];
242  activePings[lastIndex] = oldActivePing;
243  if (currentPingIndex == lastIndex)
244  {
245  currentPingIndex = pingIndex;
246  }
247  }
248  else
249  {
250  ++pingIndex;
251  }
252  }
253  }
254 
255  private IEnumerable<AITarget> GetAITargets()
256  {
257  if (!UseTransducers)
258  {
259  if (item.AiTarget != null) { yield return item.AiTarget; }
260  }
261  else
262  {
263  foreach (var transducer in connectedTransducers)
264  {
265  if (transducer.Transducer.Item.AiTarget != null)
266  {
267  yield return transducer.Transducer.Item.AiTarget;
268  }
269  }
270  }
271  }
272 
276  public override float GetCurrentPowerConsumption(Connection connection = null)
277  {
278  if (connection != powerIn || !IsActive)
279  {
280  return 0;
281  }
282 
283  return (currentMode == Mode.Active) ? powerConsumption : powerConsumption * PassivePowerConsumption;
284  }
285 
286  public override bool Use(float deltaTime, Character character = null)
287  {
288  return currentPingIndex != -1 && (character == null || characterUsable);
289  }
290 
291  private static readonly Dictionary<string, List<Character>> targetGroups = new Dictionary<string, List<Character>>();
292 
293  public override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
294  {
295  if (currentMode == Mode.Passive || !aiPingCheckPending) { return false; }
296 
297  foreach (List<Character> targetGroup in targetGroups.Values)
298  {
299  targetGroup.Clear();
300  }
301  foreach (Character c in Character.CharacterList)
302  {
303  if (c.IsDead || c.Removed || !c.Enabled) { continue; }
304  if (c.AnimController.CurrentHull != null || c.Params.HideInSonar) { continue; }
305  if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) { continue; }
306  if (Vector2.DistanceSquared(c.WorldPosition, item.WorldPosition) > range * range) { continue; }
307 
308  #warning This is not the best key for a dictionary.
309  string directionName = GetDirectionName(c.WorldPosition - item.WorldPosition).Value;
310  if (!targetGroups.ContainsKey(directionName))
311  {
312  targetGroups.Add(directionName, new List<Character>());
313  }
314  targetGroups[directionName].Add(c);
315  }
316 
317  foreach (KeyValuePair<string, List<Character>> targetGroup in targetGroups)
318  {
319  if (!targetGroup.Value.Any()) { continue; }
320  string dialogTag = "DialogSonarTarget";
321  if (targetGroup.Value.Count > 1)
322  {
323  dialogTag = "DialogSonarTargetMultiple";
324  }
325  else if (targetGroup.Value[0].Mass > 100.0f)
326  {
327  dialogTag = "DialogSonarTargetLarge";
328  }
329 
330  if (character.IsOnPlayerTeam)
331  {
332  character.Speak(TextManager.GetWithVariables(dialogTag,
333  ("[direction]", targetGroup.Key.ToString(), FormatCapitals.Yes),
334  ("[count]", targetGroup.Value.Count.ToString(), FormatCapitals.No)).Value,
335  null, 0, $"sonartarget{targetGroup.Value[0].ID}".ToIdentifier(), 60);
336  }
337 
338  //prevent the character from reporting other targets in the group
339  for (int i = 1; i < targetGroup.Value.Count; i++)
340  {
341  character.DisableLine("sonartarget" + targetGroup.Value[i].ID);
342  }
343  }
344 
345  return true;
346  }
347 
348  private LocalizedString GetDirectionName(Vector2 dir)
349  {
350  float angle = MathUtils.WrapAngleTwoPi((float)-Math.Atan2(dir.Y, dir.X) + MathHelper.PiOver2);
351 
352  int clockDir = (int)Math.Round((angle / MathHelper.TwoPi) * 12);
353  if (clockDir == 0) clockDir = 12;
354 
355  return TextManager.GetWithVariable("roomname.subdiroclock", "[dir]", clockDir.ToString());
356  }
357 
358  public override void ReceiveSignal(Signal signal, Connection connection)
359  {
360  base.ReceiveSignal(signal, connection);
361 
362  if (connection.Name == "transducer_in")
363  {
364  var transducer = signal.source.GetComponent<SonarTransducer>();
365  if (transducer == null) { return; }
366 
367  transducer.ConnectedSonar = this;
368 
369  var connectedTransducer = connectedTransducers.Find(t => t.Transducer == transducer);
370  if (connectedTransducer == null)
371  {
372  connectedTransducers.Add(new ConnectedTransducer(transducer, signal.strength, 1.0f));
373  }
374  else
375  {
376  connectedTransducer.SignalStrength = signal.strength;
377  connectedTransducer.DisconnectTimer = 1.0f;
378  }
379  }
380  }
381 
382  public void ServerEventRead(IReadMessage msg, Client c)
383  {
384  bool isActive = msg.ReadBoolean();
385  bool directionalPing = useDirectionalPing;
386  float zoomT = zoom, pingDirectionT = 0.0f;
387  bool mineralScanner = UseMineralScanner;
388  if (isActive)
389  {
390  zoomT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
391  directionalPing = msg.ReadBoolean();
392  if (directionalPing)
393  {
394  pingDirectionT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
395  }
396  mineralScanner = msg.ReadBoolean();
397  }
398 
399  if (!item.CanClientAccess(c)) { return; }
400 
401  CurrentMode = isActive ? Mode.Active : Mode.Passive;
402 
403  if (isActive)
404  {
405  zoom = MathHelper.Lerp(MinZoom, MaxZoom, zoomT);
406  useDirectionalPing = directionalPing;
407  if (useDirectionalPing)
408  {
409  float pingAngle = MathHelper.Lerp(0.0f, MathHelper.TwoPi, pingDirectionT);
410  pingDirection = new Vector2((float)Math.Cos(pingAngle), (float)Math.Sin(pingAngle));
411  }
412  UseMineralScanner = mineralScanner;
413 #if CLIENT
414  zoomSlider.BarScroll = zoomT;
415  directionalModeSwitch.Selected = useDirectionalPing;
416  if (mineralScannerSwitch != null)
417  {
418  mineralScannerSwitch.Selected = UseMineralScanner;
419  }
420 #endif
421  }
422 #if SERVER
423  item.CreateServerEvent(this);
424 #endif
425  }
426 
427  public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
428  {
429  msg.WriteBoolean(currentMode == Mode.Active);
430  if (currentMode == Mode.Active)
431  {
432  msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8);
433  msg.WriteBoolean(useDirectionalPing);
434  if (useDirectionalPing)
435  {
436  float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
437  msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
438  }
440  }
441  }
442  }
443 }
Vector2? SectorDir
Definition: AITarget.cs:79
AITarget AiTarget
Definition: Entity.cs:55
virtual Vector2 WorldPosition
Definition: Entity.cs:49
void Use(float deltaTime, Character user=null, Limb targetLimb=null, Entity useTarget=null, Character userForOnUsedEvent=null)
float powerConsumption
The maximum amount of power the item can draw from connected items
Definition: Powered.cs:94
void UpdateOnActiveEffects(float deltaTime)
Definition: Powered.cs:194
const float PassivePowerConsumption
Definition: Sonar.cs:19
override void Update(float deltaTime, Camera cam)
Definition: Sonar.cs:174
Sonar(Item item, ContentXElement element)
Definition: Sonar.cs:163
void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData=null)
Definition: Sonar.cs:427
void ServerEventRead(IReadMessage msg, Client c)
Definition: Sonar.cs:382
override void ReceiveSignal(Signal signal, Connection connection)
Definition: Sonar.cs:358
IEnumerable< SonarTransducer > ConnectedTransducers
Definition: Sonar.cs:77
override bool Use(float deltaTime, Character character=null)
Definition: Sonar.cs:286
override float GetCurrentPowerConsumption(Connection connection=null)
Power consumption of the sonar. Only consume power when active and adjust the consumption based on th...
Definition: Sonar.cs:276
override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
true if the operation was completed
Definition: Sonar.cs:293
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)