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