Server LuaCsForBarotrauma
PowerTransfer.cs
2 using Microsoft.Xna.Framework;
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Xml.Linq;
7 
9 {
10  partial class PowerTransfer : Powered
11  {
12  public List<Connection> PowerConnections { get; private set; }
13 
14  private readonly HashSet<Connection> signalConnections = new HashSet<Connection>();
15 
16  private readonly Dictionary<Connection, bool> connectionDirty = new Dictionary<Connection, bool>();
17 
18  //a list of connections a given connection is connected to, either directly or via other power transfer components
19  private readonly Dictionary<Connection, HashSet<Connection>> connectedRecipients = new Dictionary<Connection, HashSet<Connection>>();
20 
21  private float overloadCooldownTimer;
22  private const float OverloadCooldown = 5.0f;
23 
24  protected float powerLoad;
25 
26  protected bool isBroken;
27 
28  public float PowerLoad
29  {
30  get
31  {
32  if (this is RelayComponent || PowerConnections.Count == 0 || PowerConnections[0].Grid == null)
33  {
34  return powerLoad;
35  }
36  return PowerConnections[0].Grid.Load;
37  }
38  set { powerLoad = value; }
39  }
40 
41  [Editable, Serialize(true, IsPropertySaveable.Yes, description: "Can the item be damaged if too much power is supplied to the power grid.")]
42  public bool CanBeOverloaded
43  {
44  get;
45  set;
46  }
47 
48  [Editable(MinValueFloat = 1.0f), Serialize(2.0f, IsPropertySaveable.Yes, description:
49  "How much power has to be supplied to the grid relative to the load before item starts taking damage. "
50  + "E.g. a value of 2 means that the grid has to be receiving twice as much power as the devices in the grid are consuming.")]
51  public float OverloadVoltage
52  {
53  get;
54  set;
55  }
56 
57  [Serialize(0.15f, IsPropertySaveable.Yes, description: "The probability for a fire to start when the item breaks."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
58  public float FireProbability
59  {
60  get;
61  set;
62  }
63 
64  [Serialize(false, IsPropertySaveable.No, description: "Is the item currently overloaded. Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
65  public bool Overload
66  {
67  get;
68  set;
69  }
70 
71  private float extraLoad;
72  private float extraLoadSetTime;
73 
78  public float ExtraLoad
79  {
80  get { return extraLoad; }
81  set
82  {
83  extraLoad = value;
84  extraLoadSetTime = (float)Timing.TotalTime;
85  }
86  }
87 
88  //can the component transfer power
89  private bool canTransfer;
90  public bool CanTransfer
91  {
92  get { return canTransfer; }
93  set
94  {
95  if (canTransfer == value) return;
96  canTransfer = value;
98  }
99  }
100 
101  public override bool IsActive
102  {
103  get
104  {
105  return base.IsActive;
106  }
107 
108  set
109  {
110  if (base.IsActive == value) return;
111  base.IsActive = value;
112  powerLoad = 0.0f;
113  currPowerConsumption = 0.0f;
114 
116  if (!base.IsActive)
117  {
118  //we need to refresh the connections here because Update won't be called on inactive components
120  }
121  }
122  }
123 
125  : base(item, element)
126  {
127  IsActive = true;
128  canTransfer = true;
129 
130  InitProjectSpecific(element);
131  }
132 
133  partial void InitProjectSpecific(XElement element);
134 
135  private static readonly HashSet<PowerTransfer> recipientsToRefresh = new HashSet<PowerTransfer>();
136  public override void UpdateBroken(float deltaTime, Camera cam)
137  {
138  base.UpdateBroken(deltaTime, cam);
139 
140  Overload = false;
141 
142  if (!isBroken)
143  {
144  powerLoad = 0.0f;
145  currPowerConsumption = 0.0f;
147  recipientsToRefresh.Clear();
148  foreach (HashSet<Connection> recipientList in connectedRecipients.Values)
149  {
150  foreach (Connection c in recipientList)
151  {
152  if (c.Item == item) { continue; }
153  var recipientPowerTransfer = c.Item.GetComponent<PowerTransfer>();
154  if (recipientPowerTransfer != null)
155  {
156  recipientsToRefresh.Add(recipientPowerTransfer);
157  }
158  }
159  }
160  foreach (PowerTransfer recipientPowerTransfer in recipientsToRefresh)
161  {
162  recipientPowerTransfer.SetAllConnectionsDirty();
163  recipientPowerTransfer.RefreshConnections();
164  }
166  isBroken = true;
167  }
168  }
169 
170 
171  private int prevSentPowerValue;
172  private string powerSignal;
173  private int prevSentLoadValue;
174  private string loadSignal;
175 
176  public override void Update(float deltaTime, Camera cam)
177  {
179 
180  if (Timing.TotalTime > extraLoadSetTime + 1.0)
181  {
182  //Decay the extra load to 0 from either positive or negative
183  if (extraLoad > 0)
184  {
185  extraLoad = Math.Max(extraLoad - 1000.0f * deltaTime, 0);
186  }
187  else
188  {
189  extraLoad = Math.Min(extraLoad + 1000.0f * deltaTime, 0);
190  }
191  }
192 
193  if (!CanTransfer) { return; }
194 
195  if (isBroken)
196  {
198  isBroken = false;
199  }
200 
201  ApplyStatusEffects(ActionType.OnActive, deltaTime);
202 
203  float powerReadingOut = 0;
204  float loadReadingOut = ExtraLoad;
205  if (powerLoad < 0)
206  {
207  powerReadingOut = -powerLoad;
208  loadReadingOut = 0;
209  }
210 
211  if (powerOut != null && powerOut.Grid != null)
212  {
213  powerReadingOut = powerOut.Grid.Power;
214  loadReadingOut = powerOut.Grid.Load;
215  }
216 
217  if (prevSentPowerValue != (int)powerReadingOut || powerSignal == null)
218  {
219  prevSentPowerValue = (int)Math.Round(powerReadingOut);
220  powerSignal = prevSentPowerValue.ToString();
221  }
222  if (prevSentLoadValue != (int)loadReadingOut || loadSignal == null)
223  {
224  prevSentLoadValue = (int)Math.Round(loadReadingOut);
225  loadSignal = prevSentLoadValue.ToString();
226  }
227  item.SendSignal(powerSignal, "power_value_out");
228  item.SendSignal(loadSignal, "load_value_out");
229 
230  //if the item can't be fixed, don't allow it to break
231  if (!item.Repairables.Any() || !CanBeOverloaded) { return; }
232 
233  float maxOverVoltage = Math.Max(OverloadVoltage, 1.0f);
234 
235  Overload = Voltage > maxOverVoltage;
236 
237  if (Overload && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
238  {
239  if (overloadCooldownTimer > 0.0f)
240  {
241  overloadCooldownTimer -= deltaTime;
242  return;
243  }
244 
245  //damage the item if voltage is too high (except if running as a client)
246  float prevCondition = item.Condition;
247  //some randomness to prevent all junction boxes from breaking at the same time
248  if (Rand.Range(0.0f, 1.0f) < 0.01f)
249  {
250  //damaged boxes are more sensitive to overvoltage (also preventing all boxes from breaking at the same time)
251  float conditionFactor = MathHelper.Lerp(5.0f, 1.0f, item.Condition / item.MaxCondition);
252  item.Condition -= deltaTime * Rand.Range(10.0f, 500.0f) * conditionFactor;
253  }
254  if (item.Condition <= 0.0f && prevCondition > 0.0f)
255  {
256  overloadCooldownTimer = OverloadCooldown;
257 #if CLIENT
258  SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
259  Vector2 baseVel = Rand.Vector(300.0f);
260  for (int i = 0; i < 10; i++)
261  {
262  var particle = GameMain.ParticleManager.CreateParticle("spark", item.WorldPosition,
263  baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
264  if (particle != null) particle.Size *= Rand.Range(0.5f, 1.0f);
265  }
266 #endif
267  float currentIntensity = GameMain.GameSession?.EventManager != null ?
269 
270  //higher probability for fires if the current intensity is low
271  if (FireProbability > 0.0f &&
272  Rand.Range(0.0f, 1.0f) < MathHelper.Lerp(FireProbability, FireProbability * 0.1f, currentIntensity))
273  {
275  }
276  }
277  }
278  }
279 
280  public override float GetConnectionPowerOut(Connection conn, float power, PowerRange minMaxPower, float load)
281  {
282  //not used in the vanilla game (junction boxes or relays don't output power)
283  return conn == powerOut ? MathHelper.Max(-(PowerConsumption + ExtraLoad), 0) : 0;
284  }
285 
286  public override bool Pick(Character picker)
287  {
288  return picker != null;
289  }
290 
291  protected void RefreshConnections()
292  {
293  var connections = item.Connections;
294  foreach (Connection c in connections)
295  {
296  if (!connectionDirty.ContainsKey(c))
297  {
298  connectionDirty[c] = true;
299  }
300  else if (!connectionDirty[c])
301  {
302  continue;
303  }
304 
305  //find all connections that are connected to this one (directly or via another PowerTransfer)
306  HashSet<Connection> tempConnected;
307  if (!connectedRecipients.ContainsKey(c))
308  {
309  tempConnected = new HashSet<Connection>();
310  connectedRecipients.Add(c, tempConnected);
311  }
312  else
313  {
314  tempConnected = connectedRecipients[c];
315  tempConnected.Clear();
316  //mark all previous recipients as dirty
317  foreach (Connection recipient in tempConnected)
318  {
319  var pt = recipient.Item.GetComponent<PowerTransfer>();
320  if (pt != null) { pt.connectionDirty[recipient] = true; }
321  }
322  }
323 
324  tempConnected.Add(c);
325  if (item.Condition > 0.0f)
326  {
327  GetConnected(c, tempConnected);
328  //go through all the PowerTransfers that we're connected to and set their connections to match the ones we just calculated
329  //(no need to go through the recursive GetConnected method again)
330  foreach (Connection recipient in tempConnected)
331  {
332  if (recipient == c) { continue; }
333  var recipientPowerTransfer = recipient.Item.GetComponent<PowerTransfer>();
334  if (recipientPowerTransfer == null) { continue; }
335  if (!recipientPowerTransfer.connectedRecipients.ContainsKey(recipient))
336  {
337  recipientPowerTransfer.connectedRecipients.Add(recipient, new HashSet<Connection>());
338  }
339  else
340  {
341  recipientPowerTransfer.connectedRecipients[recipient].Clear();
342  }
343  foreach (var connection in tempConnected)
344  {
345  recipientPowerTransfer.connectedRecipients[recipient].Add(connection);
346  }
347  recipientPowerTransfer.connectionDirty[recipient] = false;
348  }
349  }
350  connectionDirty[c] = false;
351  }
352  }
353 
354  //Finds all the connections that can receive a signal sent into the given connection and stores them in the hashset.
355  private void GetConnected(Connection c, HashSet<Connection> connected)
356  {
357  var recipients = c.Recipients;
358 
359  foreach (Connection recipient in recipients)
360  {
361  if (recipient == null || connected.Contains(recipient)) { continue; }
362 
363  Item it = recipient.Item;
364  if (it == null || it.Condition <= 0.0f) { continue; }
365 
366  connected.Add(recipient);
367 
368  var powerTransfer = it.GetComponent<PowerTransfer>();
369  if (powerTransfer != null && powerTransfer.CanTransfer && powerTransfer.IsActive)
370  {
371  GetConnected(recipient, connected);
372  }
373  }
374  }
375 
377  {
378  if (item.Connections == null) { return; }
379  foreach (Connection c in item.Connections)
380  {
381  connectionDirty[c] = true;
382  if (c.IsPower)
383  {
384  ChangedConnections.Add(c);
385  if (connectedRecipients.TryGetValue(c, out var recipients))
386  {
387  recipients.Where(c => c.IsPower).ForEach(c => ChangedConnections.Add(c));
388  }
389  }
390  }
391  }
392 
393  public void SetConnectionDirty(Connection connection)
394  {
395  var connections = item.Connections;
396  if (connections == null || !connections.Contains(connection)) { return; }
397  connectionDirty[connection] = true;
398  if (connection.IsPower)
399  {
400  ChangedConnections.Add(connection);
401  if (connectedRecipients.TryGetValue(connection, out var recipients))
402  {
403  recipients.Where(c => c.IsPower).ForEach(c => ChangedConnections.Add(c));
404  }
405  }
406  }
407 
408  public override void OnItemLoaded()
409  {
410  base.OnItemLoaded();
411  var connections = Item.Connections;
412  PowerConnections = connections == null ? new List<Connection>() : connections.FindAll(c => c.IsPower);
413  if (connections == null)
414  {
415  IsActive = false;
416  return;
417  }
418 
419  foreach (Connection c in connections)
420  {
421  if (c.Name.Length > 5 && c.Name.Substring(0, 6) == "signal")
422  {
423  signalConnections.Add(c);
424  }
425  }
426 
427  if (this is not RelayComponent)
428  {
429  if (PowerConnections.Any(p => !p.IsOutput) && PowerConnections.Any(p => p.IsOutput))
430  {
431  DebugConsole.ThrowError("Error in item \"" + Name + "\" - PowerTransfer components should not have separate power inputs and outputs, but transfer power between wires connected to the same power connection. " +
432  "If you want power to pass from input to output, change the component to a RelayComponent.");
433  }
434  }
435 
437  }
438 
439  public override void ReceiveSignal(Signal signal, Connection connection)
440  {
441  if (item.Condition <= 0.0f || connection.IsPower) { return; }
442  if (!connectedRecipients.ContainsKey(connection)) { return; }
443  if (!signalConnections.Contains(connection)) { return; }
444 
445  foreach (Connection recipient in connectedRecipients[connection])
446  {
447  if (recipient.Item == item || recipient.Item == signal.source) { continue; }
448 
449  signal.source?.LastSentSignalRecipients.Add(recipient);
450 
451  foreach (ItemComponent ic in recipient.Item.Components)
452  {
453  //other junction boxes don't need to receive the signal in the pass-through signal connections
454  //because we relay it straight to the connected items without going through the whole chain of junction boxes
455  if (ic is PowerTransfer && ic is not RelayComponent) { continue; }
456  ic.ReceiveSignal(signal, recipient);
457  }
458 
459  if (recipient.Effects != null && signal.value != "0" && !string.IsNullOrEmpty(signal.value))
460  {
461  foreach (StatusEffect effect in recipient.Effects)
462  {
463  recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f);
464  }
465  }
466  }
467  }
468 
469  protected override void RemoveComponentSpecific()
470  {
471  base.RemoveComponentSpecific();
472  connectedRecipients?.Clear();
473  connectionDirty?.Clear();
474  recipientsToRefresh.Clear();
475  }
476  }
477 }
static NetworkMember NetworkMember
Definition: GameMain.cs:41
static GameSession GameSession
Definition: GameMain.cs:45
readonly EventManager EventManager
Definition: GameSession.cs:69
void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character=null, Limb limb=null, Entity useTarget=null, bool isNetworkEvent=false, bool checkCondition=true, Vector2? worldPosition=null)
void SendSignal(string signal, string connectionName)
readonly List< StatusEffect > Effects
Definition: Connection.cs:42
The base class for components holding the different functionalities of the item
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)
float ExtraLoad
Additional load coming from somewhere else than the devices connected to the junction box (e....
PowerTransfer(Item item, ContentXElement element)
override bool Pick(Character picker)
a Character has picked the item
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
override void ReceiveSignal(Signal signal, Connection connection)
override void Update(float deltaTime, Camera cam)
override float GetConnectionPowerOut(Connection conn, float power, PowerRange minMaxPower, float load)
Finalize how much power the device will be outputting to the connection
override void UpdateBroken(float deltaTime, Camera cam)
void SetConnectionDirty(Connection connection)
float currPowerConsumption
The amount of power currently consumed by the item. Negative values mean that the item is providing p...
Definition: Powered.cs:79
static readonly HashSet< Connection > ChangedConnections
Definition: Powered.cs:72
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
Definition: StatusEffect.cs:72
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:26