Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Items/Components/Signal/WifiComponent.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.Globalization;
6 using System.Linq;
7 using System.Xml.Linq;
8 
10 {
12  {
13  private static readonly List<WifiComponent> list = new List<WifiComponent>();
14 
15  const int ChannelMemorySize = 10;
16 
17  private const int MinChannel = 0;
18  private const int MaxChannel = 10000;
19 
20  private float range;
21 
22  private int channel;
23 
24  private float chatMsgCooldown;
25 
26  private string prevSignal;
27 
28  private int[] channelMemory = new int[ChannelMemorySize];
29 
30  private Connection signalInConnection;
31  private Connection signalOutConnection;
32 
33  [Serialize(CharacterTeamType.None, IsPropertySaveable.Yes, description: "WiFi components can only communicate with components that have the same Team ID.", alwaysUseInstanceValues: true)]
34  public CharacterTeamType TeamID { get; set; }
35 
36  [Editable, Serialize(20000.0f, IsPropertySaveable.No, description: "How close the recipient has to be to receive a signal from this WiFi component.", alwaysUseInstanceValues: true)]
37  public float Range
38  {
39  get { return range; }
40  set
41  {
42  range = Math.Max(value, 0.0f);
43 #if CLIENT
45 #endif
46  }
47  }
48 
49  [InGameEditable, Serialize(0, IsPropertySaveable.Yes, description: "WiFi components can only communicate with components that use the same channel.", alwaysUseInstanceValues: true)]
50  public int Channel
51  {
52  get { return channel; }
53  set
54  {
55  channel = MathHelper.Clamp(value, MinChannel, MaxChannel);
56  }
57  }
58 
59 
60  [Editable, Serialize(false, IsPropertySaveable.Yes, description: "Can the component communicate with wifi components in another team's submarine (e.g. enemy sub in Combat missions, respawn shuttle). Needs to be enabled on both the component transmitting the signal and the component receiving it.", alwaysUseInstanceValues: true)]
62  {
63  get;
64  set;
65  }
66 
67  [ConditionallyEditable(ConditionallyEditable.ConditionType.AllowLinkingWifiToChat, onlyInEditors: false)]
68  [Serialize(false, IsPropertySaveable.No, description: "If enabled, any signals received from another chat-linked wifi component are displayed " +
69  "as chat messages in the chatbox of the player holding the item.", alwaysUseInstanceValues: true)]
70  public bool LinkToChat
71  {
72  get;
73  set;
74  }
75 
76  [Editable, Serialize(1.0f, IsPropertySaveable.Yes, description: "How many seconds have to pass between signals for a message to be displayed in the chatbox. " +
77  "Setting this to a very low value is not recommended, because it may cause an excessive amount of chat messages to be created " +
78  "if there are chat-linked wifi components that transmit a continuous signal.")]
80  {
81  get;
82  set;
83  }
84 
85  [Editable, Serialize(false, IsPropertySaveable.Yes, description: "If set to true, the component will only create chat messages when the received signal changes.")]
87  {
88  get;
89  set;
90  }
91 
92  private float jamTimer;
93  public float JamTimer
94  {
95  get { return jamTimer; }
96  set
97  {
98  if (value > 0)
99  {
100 #if CLIENT
101  if (jamTimer <= 0)
102  {
103  HintManager.OnRadioJammed(Item);
104  }
105 #endif
106  IsActive = true;
107  }
108  jamTimer = Math.Max(0, value);
109  }
110  }
111 
113  : base (item, element)
114  {
115  list.Add(this);
116  IsActive = true;
117  }
118 
119  public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
120  {
121  base.Load(componentElement, usePrefabValues, idRemap, isItemSwap);
122  channelMemory = componentElement.GetAttributeIntArray("channelmemory", new int[ChannelMemorySize]);
123  if (channelMemory.Length != ChannelMemorySize)
124  {
125  DebugConsole.AddWarning($"Error when loading item {item.Prefab.Identifier}: the size of the channel memory doesn't match the default value of {ChannelMemorySize}. Resizing...");
126  Array.Resize(ref channelMemory, ChannelMemorySize);
127  }
128  }
129 
130  public override void OnItemLoaded()
131  {
132  if (item.Connections != null)
133  {
134  signalOutConnection = item.Connections.Find(c => c.Name == "signal_out");
135  signalInConnection = item.Connections.Find(c => c.Name == "signal_in");
136  }
137  if (channelMemory.All(m => m == 0))
138  {
139  for (int i = 0; i < channelMemory.Length; i++)
140  {
141  channelMemory[i] = i;
142  }
143  }
144  }
145 
146  public bool CanTransmit(bool ignoreJamming = false)
147  {
148  if (!ignoreJamming)
149  {
150  if (jamTimer > 0) { return false; }
151  }
152  return HasRequiredContainedItems(user: null, addMessage: false);
153  }
154 
158  public IEnumerable<WifiComponent> GetReceiversInRange()
159  {
160  return list.Where(w => w != this && w.CanReceive(this));
161  }
162 
163  public bool CanReceive(WifiComponent sender)
164  {
165  if (sender == null || sender.channel != channel) { return false; }
166  if (sender.TeamID != TeamID && !AllowCrossTeamCommunication) { return false; }
167  if (jamTimer > 0) { return false; }
168 
169  //if the component is not linked to chat and has nothing connected to the output, sending a signal to it does nothing
170  // = no point in receiving
171  if (!LinkToChat)
172  {
173  if (signalOutConnection == null || !signalOutConnection.IsConnectedToSomething())
174  {
175  return false;
176  }
177  }
178 
179  if (Vector2.DistanceSquared(item.WorldPosition, sender.item.WorldPosition) > sender.range * sender.range) { return false; }
180 
181  return HasRequiredContainedItems(user: null, addMessage: false);
182  }
183 
187  public IEnumerable<WifiComponent> GetTransmittersInRange()
188  {
189  return list.Where(w => w != this && w.CanTransmit(this));
190  }
191 
192  public bool CanTransmit(WifiComponent sender)
193  {
194  if (sender == null || sender.channel != channel) { return false; }
195  if (sender.TeamID != TeamID && !AllowCrossTeamCommunication) { return false; }
196  if (Vector2.DistanceSquared(item.WorldPosition, sender.item.WorldPosition) > sender.range * sender.range) { return false; }
197  if (jamTimer > 0) { return false; }
198  return HasRequiredContainedItems(user: null, addMessage: false);
199  }
200 
201  public override void Update(float deltaTime, Camera cam)
202  {
203  chatMsgCooldown -= deltaTime;
204  JamTimer -= deltaTime;
205  ApplyStatusEffects(ActionType.OnActive, deltaTime);
206  if (chatMsgCooldown <= 0.0f && JamTimer <= 0.0f)
207  {
208  IsActive = false;
209  }
210  }
211 
212  public int GetChannelMemory(int index)
213  {
214  if (index < 0 || index >= ChannelMemorySize)
215  {
216  return 0;
217  }
218  return channelMemory[index];
219  }
220 
221  public void SetChannelMemory(int index, int value)
222  {
223  if (index < 0 || index >= ChannelMemorySize)
224  {
225  return;
226  }
227  channelMemory[index] = MathHelper.Clamp(value, 0, 10000);
228  }
229 
230  public void TransmitSignal(Signal signal, bool sentFromChat)
231  {
232  var should = GameMain.LuaCs.Hook.Call<bool?>("wifiSignalTransmitted", this, signal, sentFromChat);
233 
234  if (should != null && should.Value)
235  return;
236 
237  if (sentFromChat)
238  {
240  }
241 
242  bool chatMsgSent = false;
243 
244  var receivers = GetReceiversInRange();
245  foreach (WifiComponent wifiComp in receivers)
246  {
247  if (sentFromChat && !wifiComp.LinkToChat) { continue; }
248 
249  //signal strength diminishes by distance
250  float sentSignalStrength = signal.strength *
251  MathHelper.Clamp(1.0f - (Vector2.Distance(item.WorldPosition, wifiComp.item.WorldPosition) / wifiComp.range), 0.0f, 1.0f);
252  Signal s = new Signal(signal.value, signal.stepsTaken + 1, sender: signal.sender, source: signal.source,
253  power: 0.0f, strength: sentSignalStrength);
254 
255  if (wifiComp.signalOutConnection != null)
256  {
257  if (signal.source != null && wifiComp.signalInConnection != null)
258  {
259  if (signal.source.LastSentSignalRecipients.Contains(wifiComp.signalInConnection))
260  {
261  //signal already passed through this wifi component -> stop here to prevent an infinite loop
262  continue;
263  }
264  else
265  {
266  signal.source.LastSentSignalRecipients.Add(wifiComp.signalInConnection);
267  }
268  }
269  wifiComp.item.SendSignal(s, wifiComp.signalOutConnection);
270  }
271 
272  if (signal.source != null)
273  {
274  foreach (Connection receiver in wifiComp.item.LastSentSignalRecipients)
275  {
276  if (!signal.source.LastSentSignalRecipients.Contains(receiver))
277  {
278  signal.source.LastSentSignalRecipients.Add(receiver);
279  }
280  }
281  }
282 
283  if (DiscardDuplicateChatMessages && signal.value == prevSignal) { continue; }
284 
285  //create a chat message
286  if (LinkToChat && wifiComp.LinkToChat && chatMsgCooldown <= 0.0f && !sentFromChat)
287  {
288  if (wifiComp.item.ParentInventory != null &&
289  wifiComp.item.ParentInventory.Owner != null)
290  {
291  string chatMsg = signal.value;
292  if (sentSignalStrength <= 1.0f)
293  {
294  chatMsg = ChatMessage.ApplyDistanceEffect(chatMsg, 1.0f - sentSignalStrength);
295  }
296  if (chatMsg.Length > ChatMessage.MaxLength) { chatMsg = chatMsg.Substring(0, ChatMessage.MaxLength); }
297  if (string.IsNullOrEmpty(chatMsg)) { continue; }
298 
299 #if CLIENT
301  {
302  if (GameMain.Client == null)
303  {
304  GameMain.GameSession?.CrewManager?.AddSinglePlayerChatMessage(signal.source?.Name ?? "", signal.value, ChatMessageType.Radio, sender: item);
305  }
306  }
307 #elif SERVER
308  if (GameMain.Server != null)
309  {
310  Client recipientClient = GameMain.Server.ConnectedClients.Find(c => c.Character == wifiComp.item.ParentInventory.Owner);
311  if (recipientClient != null)
312  {
313  GameMain.Server.SendDirectChatMessage(
314  ChatMessage.Create(signal.source?.Name ?? "", chatMsg, ChatMessageType.Radio, item), recipientClient);
315  }
316  }
317 #endif
318  chatMsgSent = true;
319  }
320  }
321  }
322  if (chatMsgSent)
323  {
324  chatMsgCooldown = MinChatMessageInterval;
325  IsActive = true;
326  }
327 
328  prevSignal = signal.value;
329  }
330 
331  public override void ReceiveSignal(Signal signal, Connection connection)
332  {
333  if (connection == null) { return; }
334 
335  switch (connection.Name)
336  {
337  case "signal_in":
338  TransmitSignal(signal, false);
339  break;
340  case "set_channel":
341  if (int.TryParse(signal.value, out int newChannel))
342  {
343  int prevChannel = Channel;
344  Channel = newChannel;
345  if (prevChannel != Channel)
346  {
347 #if SERVER
348  item.CreateServerEvent(this);
349 #endif
350  }
351  }
352  break;
353  case "set_range":
354  if (float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float newRange))
355  {
356  Range = newRange;
357  }
358  break;
359  }
360  }
361 
362  protected override void RemoveComponentSpecific()
363  {
364  base.RemoveComponentSpecific();
365  list.Remove(this);
366  }
367 
368  public override XElement Save(XElement parentElement)
369  {
370  var element = base.Save(parentElement);
371  element.Add(new XAttribute("channelmemory", string.Join(',', channelMemory)));
372  return element;
373  }
374  }
375 }
int?[] GetAttributeIntArray(string key, int[]? def)
void AddSinglePlayerChatMessage(LocalizedString senderName, LocalizedString text, ChatMessageType messageType, Entity sender)
Adds the message to the single player chatbox.
static GameSession?? GameSession
Definition: GameMain.cs:88
static GameClient Client
Definition: GameMain.cs:188
static LuaCsSetup LuaCs
Definition: GameMain.cs:26
List< Connection > LastSentSignalRecipients
A list of connections the last signal sent by this item went through
void SendSignal(string signal, string connectionName)
bool IsConnectedToSomething()
Checks if the the connection is connected to a wire or a circuit box connection
The base class for components holding the different functionalities of the item
bool HasRequiredContainedItems(Character user, bool addMessage, LocalizedString msg=null)
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb targetLimb=null, Entity useTarget=null, Character user=null, Vector2? worldPosition=null, float afflictionMultiplier=1.0f)
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
IEnumerable< WifiComponent > GetReceiversInRange()
Returns the wifi components that can receive signals from this one
IEnumerable< WifiComponent > GetTransmittersInRange()
Returns the wifi components that can transmit signals to this one
object Call(string name, params object[] args)
static ChatMessage Create(string senderName, string text, ChatMessageType type, Entity sender, Client client=null, PlayerConnectionChangeType changeType=PlayerConnectionChangeType.None, Color? textColor=null)
Interface for entities that the server can send events to the clients
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19