Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Items/Components/Signal/ConnectionPanel.cs
1 using System;
3 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Xml.Linq;
7 
9 {
11  {
12  const int MaxConnectionCount = 256;
13  public readonly List<Connection> Connections = new List<Connection>();
14 
15  private Character user;
16 
20  public readonly HashSet<Wire> DisconnectedWires = new HashSet<Wire>();
21 
22  private List<ushort> disconnectedWireIds;
23 
27  public bool AlwaysAllowRewiring
28  {
29  get
30  {
31  if (item.Submarine == null) { return true; }
32  switch (item.Submarine.Info.Type)
33  {
34  case SubmarineType.Wreck:
35  case SubmarineType.BeaconStation:
36  case SubmarineType.EnemySubmarine:
37  case SubmarineType.Ruin:
38  return true;
39  }
40  return false;
41  }
42  }
43 
44  [Editable, Serialize(false, IsPropertySaveable.Yes, description: "Locked connection panels cannot be rewired in-game.", alwaysUseInstanceValues: true)]
45  public bool Locked
46  {
47  get;
48  set;
49  }
50 
51  public bool TemporarilyLocked
52  {
53  get { return Level.IsLoadedOutpost && (item.GetComponent<DockingPort>()?.Docked ?? false); }
54  }
55 
56  //connection panels can't be deactivated externally (by signals or status effects)
57  public override bool IsActive
58  {
59  get { return base.IsActive; }
60  set { /*do nothing*/ }
61  }
62 
63  public Character User
64  {
65  get { return user; }
66  }
67 
69  : base(item, element)
70  {
71  foreach (var subElement in element.Elements())
72  {
73  if (Connections.Count == MaxConnectionCount)
74  {
75  DebugConsole.ThrowError($"Too many connections in the item {item.Prefab.Identifier} (> {MaxConnectionCount}).");
76  break;
77  }
78  switch (subElement.Name.ToString())
79  {
80  case "input":
81  Connections.Add(new Connection(subElement, this, IdRemap.DiscardId));
82  break;
83  case "output":
84  Connections.Add(new Connection(subElement, this, IdRemap.DiscardId));
85  break;
86  }
87  }
88 
89  base.IsActive = true;
90  InitProjSpecific();
91  }
92 
93  partial void InitProjSpecific();
94 
95  private bool linksInitialized;
96  public override void OnMapLoaded()
97  {
98  if (linksInitialized) { return; }
100  }
101 
102  public void InitializeLinks()
103  {
104  foreach (Connection c in Connections)
105  {
107  }
108 
109  if (disconnectedWireIds != null)
110  {
111  foreach (ushort disconnectedWireId in disconnectedWireIds)
112  {
113  if (!(Entity.FindEntityByID(disconnectedWireId) is Item wireItem)) { continue; }
114  Wire wire = wireItem.GetComponent<Wire>();
115  if (wire != null)
116  {
117  if (Item.ItemList.Any(it => it != item && (it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false)))
118  {
119  if (wire.Item.body != null) { wire.Item.body.Enabled = false; }
120  wire.IsActive = false;
121  wire.UpdateSections();
122  }
123  DisconnectedWires.Add(wire);
124  base.IsActive = true;
125  }
126  }
127  }
128 
129  linksInitialized = true;
130  }
131 
132  public override void OnItemLoaded()
133  {
134  if (item.body != null && item.body.BodyType == FarseerPhysics.BodyType.Dynamic)
135  {
136  var holdable = item.GetComponent<Holdable>();
137  if (holdable == null || !holdable.Attachable)
138  {
139  DebugConsole.ThrowError("Item \"" + item.Name + "\" has a ConnectionPanel component," +
140  " but cannot be wired because it has an active physics body that cannot be attached to a wall." +
141  " Remove the physics body or add a Holdable component with the Attachable attribute set to true.");
142  }
143  }
144  }
145 
146  public void MoveConnectedWires(Vector2 amount)
147  {
148  Vector2 wireNodeOffset = item.Submarine == null ? Vector2.Zero : item.Submarine.HiddenSubPosition + amount;
149  foreach (Connection c in Connections)
150  {
151  foreach (Wire wire in c.Wires)
152  {
153  if (wire == null) { continue; }
154  TryMoveWire(wire);
155  }
156  }
157 
158  foreach (var wire in DisconnectedWires)
159  {
160  TryMoveWire(wire);
161  }
162 
163  void TryMoveWire(Wire wire)
164  {
165 #if CLIENT
166  if (wire.Item.IsSelected) { return; }
167 #endif
168  var wireNodes = wire.GetNodes();
169  if (wireNodes.Count == 0) { return; }
170 
171  if (Submarine.RectContains(item.Rect, wireNodes[0] + wireNodeOffset))
172  {
173  wire.MoveNode(0, amount);
174  }
175  else if (Submarine.RectContains(item.Rect, wireNodes[wireNodes.Count - 1] + wireNodeOffset))
176  {
177  wire.MoveNode(wireNodes.Count - 1, amount);
178  }
179  }
180  }
181 
182  public override void Update(float deltaTime, Camera cam)
183  {
184  UpdateProjSpecific(deltaTime);
185 
186  if (user == null || (user.SelectedItem != item && user.SelectedSecondaryItem != item))
187  {
188 #if SERVER
189  if (user != null) { item.CreateServerEvent(this); }
190 #endif
191  user = null;
192  if (DisconnectedWires.Count == 0) { base.IsActive = false; }
193  return;
194  }
195 
196  if (!user.Enabled || !HasRequiredItems(user, addMessage: false))
197  {
198  user = null;
199  base.IsActive = false;
200  return;
201  }
202 
203  user.AnimController.UpdateUseItem(!user.IsClimbing, item.WorldPosition + new Vector2(0.0f, 100.0f) * (((float)Timing.TotalTime / 10.0f) % 0.1f));
204  }
205 
206  public override void UpdateBroken(float deltaTime, Camera cam)
207  {
208  Update(deltaTime, cam);
209  }
210 
211  partial void UpdateProjSpecific(float deltaTime);
212 
213  public bool CanRewire()
214  {
215  if (item.Container?.GetComponent<CircuitBox>() != null)
216  {
217  return true;
218  }
219  //attaching wires to items with a body is not allowed
220  //(signal items remove their bodies when attached to a wall)
221  if (item.body != null && item.body.BodyType == FarseerPhysics.BodyType.Dynamic)
222  {
223  return false;
224  }
225  return true;
226  }
227 
228  public override bool Select(Character picker)
229  {
230  if (!CanRewire())
231  {
232  return false;
233  }
234 
235  user = picker;
236 #if SERVER
237  if (user != null) { item.CreateServerEvent(this); }
238 #endif
239  base.IsActive = true;
240  return true;
241  }
242 
243  public override bool Use(float deltaTime, Character character = null)
244  {
245  if (character == null || character != user) { return false; }
246  return true;
247  }
248 
252  public bool CheckCharacterSuccess(Character character)
253  {
254  if (character == null) { return false; }
255  //no electrocution in sub editor
256  if (Screen.Selected == GameMain.SubEditorScreen) { return true; }
257 
258  var reactor = item.GetComponent<Reactor>();
259  if (reactor != null)
260  {
261  //reactors that arent generating power atm can be rewired without the risk of electrical shock
262  if (MathUtils.NearlyEqual(reactor.CurrPowerConsumption, 0.0f)) { return true; }
263  }
264  var powerContainer = item.GetComponent<PowerContainer>();
265  if (powerContainer != null)
266  {
267  //empty batteries/supercapacitors can be rewired without the risk of electrical shock
268  //non-empty ones always have a chance of zapping the user
269  if (powerContainer.Charge <= 0.0f) { return true; }
270  }
271  var powered = item.GetComponent<Powered>();
272  if (powered != null && powerContainer == null)
273  {
274  //unpowered panels can be rewired without the risk of electrical shock
275  if (powered.Voltage < 0.1f) { return true; }
276  }
277 
278  float degreeOfSuccess = DegreeOfSuccess(character);
279  if (Rand.Range(0.0f, 0.5f) < degreeOfSuccess) { return true; }
280 
281  ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
282  return false;
283  }
284 
285  public override void Load(ContentXElement element, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
286  {
287  base.Load(element, usePrefabValues, idRemap, isItemSwap);
288 
289  List<Connection> loadedConnections = new List<Connection>();
290 
291  foreach (var subElement in element.Elements())
292  {
293  switch (subElement.Name.ToString())
294  {
295  case "input":
296  loadedConnections.Add(new Connection(subElement, this, idRemap));
297  break;
298  case "output":
299  loadedConnections.Add(new Connection(subElement, this, idRemap));
300  break;
301  }
302  }
303 
304  for (int i = 0; i < loadedConnections.Count && i < Connections.Count; i++)
305  {
306  Connections[i].LoadedWires.Clear();
307  Connections[i].LoadedWires.AddRange(loadedConnections[i].LoadedWires);
308  }
309 
310  disconnectedWireIds = element.GetAttributeUshortArray("disconnectedwires", Array.Empty<ushort>()).ToList();
311  for (int i = 0; i < disconnectedWireIds.Count; i++)
312  {
313  disconnectedWireIds[i] = idRemap.GetOffsetId(disconnectedWireIds[i]);
314  }
315  }
316 
317  public override XElement Save(XElement parentElement)
318  {
319  XElement componentElement = base.Save(parentElement);
320 
321  foreach (Connection c in Connections)
322  {
323  c.Save(componentElement);
324  }
325 
326  if (DisconnectedWires.Count > 0)
327  {
328  componentElement.Add(new XAttribute("disconnectedwires", string.Join(",", DisconnectedWires.Select(w => w.Item.ID))));
329  }
330 
331  return componentElement;
332  }
333 
334  protected override void ShallowRemoveComponentSpecific()
335  {
336  //do nothing
337  }
338 
339  protected override void RemoveComponentSpecific()
340  {
341  base.RemoveComponentSpecific();
342  foreach (Wire wire in DisconnectedWires.ToList())
343  {
344  if (wire.OtherConnection(null) == null) //wire not connected to anything else
345  {
346 #if CLIENT
348  {
349  wire.Item.Remove();
350  }
351  else
352  {
353  wire.Item.Drop(null);
354  }
355 #else
356  wire.Item.Drop(null);
357 #endif
358  }
359  }
360 
361  DisconnectedWires.Clear();
362  foreach (Connection c in Connections)
363  {
364  foreach (Wire wire in c.Wires.ToArray())
365  {
366  if (wire.OtherConnection(c) == null) //wire not connected to anything else
367  {
368 #if CLIENT
370  {
371  wire.Item.Remove();
372  }
373  else
374  {
375  wire.Item.Drop(null);
376  }
377 #else
378  wire.Item.Drop(null);
379 #endif
380  }
381  else
382  {
383  wire.RemoveConnection(item);
384  }
385  }
386  c.Grid = null;
387  }
388  foreach (var connection in Connections)
389  {
390  Powered.ChangedConnections.Remove(connection);
391  connection.Recipients.Clear();
392  }
393  Connections.Clear();
394 
395 #if CLIENT
396  rewireSoundChannel?.FadeOutAndDispose();
397  rewireSoundChannel = null;
398 #endif
399  }
400 
401  public override void ReceiveSignal(Signal signal, Connection connection)
402  {
403  //do nothing
404  }
405 
406 
407  public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
408  {
409 #if CLIENT
411 #endif
412  msg.WriteByte((byte)Connections.Count);
413  foreach (Connection connection in Connections)
414  {
415  msg.WriteVariableUInt32((uint)connection.Wires.Count);
416  foreach (Wire wire in connection.Wires)
417  {
418  msg.WriteUInt16(wire?.Item == null ? (ushort)0 : wire.Item.ID);
419  }
420  }
421 
422  msg.WriteUInt16((ushort)DisconnectedWires.Count);
423  foreach (Wire disconnectedWire in DisconnectedWires)
424  {
425  msg.WriteUInt16(disconnectedWire.Item.ID);
426  }
427  }
428  }
429 }
void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
Item SelectedSecondaryItem
The secondary selected item. It's an item other than a device (see SelectedItem), e....
Submarine Submarine
Definition: Entity.cs:53
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
Definition: Entity.cs:43
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
Definition: Entity.cs:204
static SubEditorScreen SubEditorScreen
Definition: GameMain.cs:68
static readonly IdRemap DiscardId
Definition: IdRemap.cs:12
void Drop(Character dropper, bool createNetworkEvent=true, bool setTransform=true)
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
static readonly List< Item > ItemList
override void OnItemLoaded()
Called when all the components of the item have been loaded. Use to initialize connections between co...
readonly HashSet< Wire > DisconnectedWires
Wires that have been disconnected from the panel, but not removed completely (visible at the bottom o...
override void OnMapLoaded()
Called when all items have been loaded. Use to initialize connections between items.
bool AlwaysAllowRewiring
Allows rewiring the connection panel despite rewiring being disabled on a server
override void Load(ContentXElement element, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
bool CheckCharacterSuccess(Character character)
Check if the character manages to succesfully rewire the panel, and if not, apply OnFailure effects
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)
virtual bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg=null)
float DegreeOfSuccess(Character character)
Returns 0.0f-1.0f based on how well the Character can use the itemcomponent
static bool IsLoadedOutpost
Is there a loaded level set and is it an outpost?
static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive=false)
Interface for entities that the clients can send events to the server
Interface for entities that the server can send events to the clients
ActionType
ActionTypes define when a StatusEffect is executed.
Definition: Enums.cs:19