Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Items/Components/Signal/CircuitBox.cs
1 #nullable enable
2 
3 using System;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Linq;
7 using System.Xml.Linq;
9 using Microsoft.Xna.Framework;
10 
12 {
13  internal sealed partial class CircuitBox : ItemComponent, IClientSerializable, IServerSerializable
14  {
15  public static readonly ImmutableHashSet<CircuitBoxOpcode> UnrealiableOpcodes
16  = ImmutableHashSet.Create(CircuitBoxOpcode.Cursor);
17 
18  public ImmutableArray<CircuitBoxInputConnection> Inputs;
19  public ImmutableArray<CircuitBoxOutputConnection> Outputs;
20 
21  public readonly List<CircuitBoxComponent> Components = new List<CircuitBoxComponent>();
22 
23  public readonly List<CircuitBoxInputOutputNode> InputOutputNodes = new();
24 
25  public readonly List<CircuitBoxLabelNode> Labels = new();
26 
27  public readonly List<CircuitBoxWire> Wires = new List<CircuitBoxWire>();
28 
29  public override bool IsActive => true;
30 
31  // We don't want the components and wires to transfer between subs as it would cause issues.
32  public override bool DontTransferInventoryBetweenSubs => true;
33 
34  // We don't want to sell the components and wires inside the circuit box
35  public override bool DisallowSellingItemsFromContainer => true;
36 
37  public Option<CircuitBoxConnection> FindInputOutputConnection(Identifier connectionName)
38  {
39  foreach (CircuitBoxInputConnection input in Inputs)
40  {
41  if (input.Name != connectionName) { continue; }
42  return Option.Some<CircuitBoxConnection>(input);
43  }
44 
45  foreach (CircuitBoxOutputConnection output in Outputs)
46  {
47  if (output.Name != connectionName) { continue; }
48  return Option.Some<CircuitBoxConnection>(output);
49  }
50 
51  return Option.None;
52  }
53 
54  public Option<CircuitBoxConnection> FindInputOutputConnection(Connection connection)
55  {
56  foreach (CircuitBoxInputConnection input in Inputs)
57  {
58  if (input.Connection != connection) { continue; }
59  return Option.Some<CircuitBoxConnection>(input);
60  }
61 
62  foreach (CircuitBoxOutputConnection output in Outputs)
63  {
64  if (output.Connection != connection) { continue; }
65  return Option.Some<CircuitBoxConnection>(output);
66  }
67 
68  return Option.None;
69  }
70 
71  public readonly ItemContainer[] containers;
72 
73  private const int ComponentContainerIndex = 0,
74  WireContainerIndex = 1;
75 
76  public ItemContainer? ComponentContainer
77  => GetContainerOrNull(ComponentContainerIndex);
78 
79  // wire container falls back to the main container if one isn't specified
80  public ItemContainer? WireContainer
81  => GetContainerOrNull(WireContainerIndex) ?? GetContainerOrNull(ComponentContainerIndex);
82 
83  public bool IsFull => ComponentContainer?.Inventory is { } inventory && inventory.IsFull(true);
84 
85  [Editable, Serialize(false, IsPropertySaveable.Yes, description: "Locked circuit boxes can only be viewed and not interacted with.")]
86  public bool Locked { get; set; }
87 
88  public CircuitBox(Item item, ContentXElement element) : base(item, element)
89  {
90  containers = item.GetComponents<ItemContainer>().ToArray();
91  if (containers.Length < 1)
92  {
93  DebugConsole.ThrowError("Circuit box must have at least one item container to function.");
94  }
95 
96  InitProjSpecific(element);
97 
98  var inputBuilder = ImmutableArray.CreateBuilder<CircuitBoxInputConnection>();
99  var outputBuilder = ImmutableArray.CreateBuilder<CircuitBoxOutputConnection>();
100 
101  foreach (Connection conn in Item.Connections)
102  {
103  if (conn.IsOutput)
104  {
105  outputBuilder.Add(new CircuitBoxOutputConnection(Vector2.Zero, conn, this));
106  }
107  else
108  {
109  inputBuilder.Add(new CircuitBoxInputConnection(Vector2.Zero, conn, this));
110  }
111  }
112 
113  Inputs = inputBuilder.ToImmutable();
114  Outputs = outputBuilder.ToImmutable();
115 
116  InputOutputNodes.Add(new CircuitBoxInputOutputNode(Inputs, new Vector2(-512, 0f), CircuitBoxInputOutputNode.Type.Input, this));
117  InputOutputNodes.Add(new CircuitBoxInputOutputNode(Outputs, new Vector2(512, 0f), CircuitBoxInputOutputNode.Type.Output, this));
118 
119  item.OnDeselect += OnDeselected;
120  }
121 
126  private Option<ContentXElement> delayedElementToLoad;
127 
128  public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
129  {
130  base.Load(componentElement, usePrefabValues, idRemap, isItemSwap);
131  if (delayedElementToLoad.IsSome()) { return; }
132  delayedElementToLoad = Option.Some(componentElement);
133  }
134 
135  public override void OnInventoryChanged()
136  => OnViewUpdateProjSpecific();
137 
138  public override void Update(float deltaTime, Camera cam)
139  {
140 #if CLIENT
141  // When loading from the server the wires cannot be properly loaded and connected up because we might not be loaded in properly yet.
142  // So we need to wait until the circuit box starts updating and then we can ensure the wires are connected.
143  if (wasInitializedByServer)
144  {
145  foreach (var w in Wires)
146  {
147  w.EnsureWireConnected();
148  }
149  wasInitializedByServer = false;
150  }
151 #endif
152  TryInitializeNodes();
153  }
154 
155  public override void OnMapLoaded()
156  => TryInitializeNodes();
157 
158  private void TryInitializeNodes()
159  {
160  if (!delayedElementToLoad.TryUnwrap(out var loadElement)) { return; }
161  LoadFromXML(loadElement);
162  delayedElementToLoad = Option.None;
163  }
164 
165  public void LoadFromXML(ContentXElement loadElement)
166  {
167  foreach (var subElement in loadElement.Elements())
168  {
169  string elementName = subElement.Name.ToString().ToLowerInvariant();
170  switch (elementName)
171  {
172  case "component" when CircuitBoxComponent.TryLoadFromXML(subElement, this).TryUnwrap(out var comp):
173  Components.Add(comp);
174  break;
175  case "wire" when CircuitBoxWire.TryLoadFromXML(subElement, this).TryUnwrap(out var wire):
176  Wires.Add(wire);
177  break;
178  case "inputnode":
179  LoadFor(CircuitBoxInputOutputNode.Type.Input, subElement);
180  break;
181  case "outputnode":
182  LoadFor(CircuitBoxInputOutputNode.Type.Output, subElement);
183  break;
184  case "label":
185  Labels.Add(CircuitBoxLabelNode.LoadFromXML(subElement, this));
186  break;
187  }
188  }
189 
190 #if SERVER
191  // We need to let the clients know of the loaded data
192  if (needsServerInitialization)
193  {
194  CreateInitializationEvent();
195  needsServerInitialization = false;
196  }
197 #endif
198 
199  void LoadFor(CircuitBoxInputOutputNode.Type type, ContentXElement subElement)
200  {
201  foreach (var node in InputOutputNodes)
202  {
203  if (node.NodeType != type) { continue; }
204 
205  node.Load(subElement);
206  break;
207  }
208  }
209  }
210 
211  public void CloneFrom(CircuitBox original, Dictionary<ushort, Item> clonedContainedItems)
212  {
213  Components.Clear();
214  Wires.Clear();
215  Labels.Clear();
216 
217  foreach (var label in original.Labels)
218  {
219  var newLabel = new CircuitBoxLabelNode(label.ID, label.Color, label.Position, this);
220  newLabel.EditText(label.HeaderText, label.BodyText);
221  newLabel.ApplyResize(label.Size, label.Position);
222  Labels.Add(newLabel);
223  }
224 
225  for (int ioIndex = 0; ioIndex < original.InputOutputNodes.Count; ioIndex++)
226  {
227  var origNode = original.InputOutputNodes[ioIndex];
228  var cloneNode = InputOutputNodes[ioIndex];
229 
230  cloneNode.Position = origNode.Position;
231  }
232 
233  if (!clonedContainedItems.Any()) { return; }
234 
235  foreach (var origComp in original.Components)
236  {
237  if (!clonedContainedItems.TryGetValue(origComp.Item.ID, out var clonedItem)) { continue; }
238  var newComponent = new CircuitBoxComponent(origComp.ID, clonedItem, origComp.Position, this, origComp.UsedResource);
239  Components.Add(newComponent);
240  }
241 
242  foreach (var origWire in original.Wires)
243  {
244  Option<CircuitBoxConnection> to = CircuitBoxConnectorIdentifier.FromConnection(origWire.To).FindConnection(this),
245  from = CircuitBoxConnectorIdentifier.FromConnection(origWire.From).FindConnection(this);
246 
247  if (!to.TryUnwrap(out var toConn) || !from.TryUnwrap(out var fromConn))
248  {
249  DebugConsole.ThrowError($"Error while cloning item \"{Name}\" - failed to find a connection for a wire. ");
250  continue;
251  }
252 
253  var wireItem = origWire.BackingWire.Select(w => clonedContainedItems[w.ID]);
254  var newWire = new CircuitBoxWire(this, origWire.ID, wireItem, fromConn, toConn, origWire.UsedItemPrefab);
255  Wires.Add(newWire);
256  }
257  }
258 
259  public override XElement Save(XElement parentElement)
260  {
261  XElement componentElement = base.Save(parentElement);
262 
263  foreach (CircuitBoxInputOutputNode node in InputOutputNodes)
264  {
265  componentElement.Add(node.Save());
266  }
267 
268  foreach (CircuitBoxComponent node in Components)
269  {
270  componentElement.Add(node.Save());
271  }
272 
273  foreach (CircuitBoxWire wire in Wires)
274  {
275  componentElement.Add(wire.Save());
276  }
277 
278  foreach (var label in Labels)
279  {
280  componentElement.Add(label.Save());
281  }
282 
283  return componentElement;
284  }
285 
286  public partial void OnDeselected(Character c);
287 
288  public record struct CreatedWire(CircuitBoxConnectorIdentifier Start, CircuitBoxConnectorIdentifier End, Option<Item> Item, ushort ID);
289 
290  public bool Connect(CircuitBoxConnection one, CircuitBoxConnection two, Action<CreatedWire> onCreated, ItemPrefab selectedWirePrefab)
291  {
292  if (!VerifyConnection(one, two)) { return false; }
293 
294  ushort id = ICircuitBoxIdentifiable.FindFreeID(Wires);
295  switch (one.IsOutput)
296  {
297  case true when !two.IsOutput:
298  {
299  CircuitBoxConnectorIdentifier start = CircuitBoxConnectorIdentifier.FromConnection(one),
300  end = CircuitBoxConnectorIdentifier.FromConnection(two);
301 
302  if (IsExternalConnection(one) || IsExternalConnection(two))
303  {
304  CreateWireWithoutItem(one, two, id, selectedWirePrefab);
305  onCreated(new CreatedWire(start, end, Option.None, id));
306  return true;
307  }
308 
309  CreateWireWithItem(one, two, selectedWirePrefab, id, i => onCreated(new CreatedWire(start, end, Option.Some(i), id)));
310  return true;
311  }
312  case false when two.IsOutput:
313  {
314  CircuitBoxConnectorIdentifier start = CircuitBoxConnectorIdentifier.FromConnection(two),
315  end = CircuitBoxConnectorIdentifier.FromConnection(one);
316  if (IsExternalConnection(one) || IsExternalConnection(two))
317  {
318  CreateWireWithoutItem(two, one, id, selectedWirePrefab);
319  onCreated(new CreatedWire(start, end, Option.None, id));
320  return true;
321  }
322 
323  CreateWireWithItem(two, one, selectedWirePrefab, id, i => onCreated(new CreatedWire(start, end, Option.Some(i), id)));
324  return true;
325  }
326  }
327 
328  return false;
329  }
330 
331  private static bool VerifyConnection(CircuitBoxConnection one, CircuitBoxConnection two)
332  {
333  if (one.IsOutput == two.IsOutput || one == two) { return false; }
334 
335  if (one is CircuitBoxNodeConnection oneNodeConnection &&
336  two is CircuitBoxNodeConnection twoNodeConnection)
337  {
338  if (oneNodeConnection.Component == twoNodeConnection.Component)
339  {
340  return false;
341  }
342  }
343 
344  if (one is CircuitBoxNodeConnection { HasAvailableSlots: false } ||
345  two is CircuitBoxNodeConnection { HasAvailableSlots: false })
346  {
347  return one is not CircuitBoxNodeConnection || two is not CircuitBoxNodeConnection;
348  }
349 
350  return true;
351  }
352 
353  private void AddLabelInternal(ushort id, Color color, Vector2 pos, NetLimitedString header, NetLimitedString body)
354  {
355  var newLabel = new CircuitBoxLabelNode(id, color, pos, this);
356  newLabel.EditText(header, body);
357  Labels.Add(newLabel);
358  OnViewUpdateProjSpecific();
359  }
360 
361  private void RemoveLabelInternal(IReadOnlyCollection<ushort> ids)
362  {
363  foreach (CircuitBoxLabelNode node in Labels.ToImmutableArray())
364  {
365  if (!ids.Contains(node.ID)) { continue; }
366  Labels.Remove(node);
367  }
368  OnViewUpdateProjSpecific();
369  }
370 
371  private void ResizeLabelInternal(ushort id, Vector2 pos, Vector2 size)
372  {
373  size = Vector2.Max(size, CircuitBoxLabelNode.MinSize);
374  foreach (CircuitBoxLabelNode node in Labels)
375  {
376  if (node.ID != id) { continue; }
377  node.ApplyResize(size, pos);
378  break;
379  }
380  OnViewUpdateProjSpecific();
381  }
382 
383  private void RenameConnectionLabelsInternal(CircuitBoxInputOutputNode.Type type, Dictionary<string, string> overrides)
384  {
385  foreach (var node in InputOutputNodes)
386  {
387  if (node.NodeType != type) { continue; }
388 
389  node.ReplaceAllConnectionLabelOverrides(overrides);
390  break;
391  }
392  OnViewUpdateProjSpecific();
393  }
394 
395  private static bool IsExternalConnection(CircuitBoxConnection conn) => conn is (CircuitBoxInputConnection or CircuitBoxOutputConnection);
396 
397  private void CreateWireWithoutItem(CircuitBoxConnection one, CircuitBoxConnection two, ushort id, ItemPrefab prefab)
398  {
399  bool hasExternalConnection = false;
400  if (one is CircuitBoxInputConnection input)
401  {
402  hasExternalConnection = true;
403  input.ExternallyConnectedTo.Add(two);
404  }
405 
406  if (two is CircuitBoxOutputConnection output)
407  {
408  hasExternalConnection = true;
409  one.Connection.CircuitBoxConnections.Add(output);
410  }
411 
412  if (hasExternalConnection)
413  {
414  two.ExternallyConnectedFrom.Add(one);
415  }
416 
417  AddWireDirect(id, prefab, Option.None, one, two);
418  }
419 
420  private void CreateWireWithItem(CircuitBoxConnection one, CircuitBoxConnection two, ItemPrefab prefab, ushort wireId, Action<Item> onItemSpawned)
421  {
422  if (WireContainer is null) { return; }
423 
424  if (IsExternalConnection(one) || IsExternalConnection(two))
425  {
426  DebugConsole.ThrowError("Cannot add a wire between an external connection and a component connection.");
427  return;
428  }
429 
430  SpawnItem(prefab, user: null, container: WireContainer, onSpawned: wire =>
431  {
432  AddWireDirect(wireId, prefab, Option.Some(wire), one, two);
433  onItemSpawned(wire);
434  });
435  }
436 
437  private void CreateWireWithItem(CircuitBoxConnection one, CircuitBoxConnection two, ushort wireId, Item it)
438  {
439  if (IsExternalConnection(one) || IsExternalConnection(two))
440  {
441  DebugConsole.ThrowError("Cannot add a wire between an external connection and a component connection.");
442  return;
443  }
444 
445  AddWireDirect(wireId, it.Prefab, Option.Some(it), one, two);
446  }
447 
448  private void AddWireDirect(ushort id, ItemPrefab prefab, Option<Item> backingItem, CircuitBoxConnection one, CircuitBoxConnection two)
449  => Wires.Add(new CircuitBoxWire(this, id, backingItem, one, two, prefab));
450 
451  private void RenameLabelInternal(ushort id, Color color, NetLimitedString header, NetLimitedString body)
452  {
453  foreach (CircuitBoxLabelNode node in Labels)
454  {
455  if (node.ID != id) { continue; }
456 
457  node.EditText(header, body);
458  node.Color = color;
459  break;
460  }
461  }
462 
463  private bool AddComponentInternal(ushort id, ItemPrefab prefab, ItemPrefab usedResource, Vector2 pos, Character? user, Action<Item>? onItemSpawned)
464  {
465  if (id is ICircuitBoxIdentifiable.NullComponentID)
466  {
467  DebugConsole.ThrowError("Unable to add component because there are no free IDs.");
468  return false;
469  }
470 
471  if (ComponentContainer?.Inventory is { } inventory && inventory.HowManyCanBePut(prefab) <= 0)
472  {
473  DebugConsole.ThrowError("Unable to add component because there is no space in the inventory.");
474  return false;
475  }
476 
477  SpawnItem(prefab, user, ComponentContainer, spawnedItem =>
478  {
479  Components.Add(new CircuitBoxComponent(id, spawnedItem, pos, this, usedResource));
480  onItemSpawned?.Invoke(spawnedItem);
481  OnViewUpdateProjSpecific();
482  });
483  OnViewUpdateProjSpecific();
484  return true;
485  }
486 
487  // Unsafe because it doesn't perform error checking since it's data we get from the server
488  private void AddComponentInternalUnsafe(ushort id, Item backingItem, ItemPrefab usedResource, Vector2 pos)
489  {
490  Components.Add(new CircuitBoxComponent(id, backingItem, pos, this, usedResource));
491  OnViewUpdateProjSpecific();
492  }
493 
494  private static void ClearSelectionFor(ushort characterId, IReadOnlyCollection<CircuitBoxSelectable> nodes)
495  {
496  foreach (var node in nodes)
497  {
498  if (node.SelectedBy != characterId) { continue; }
499 
500  node.SetSelected(Option.None);
501  }
502  }
503 
504  private void ClearAllSelectionsInternal(ushort characterId)
505  {
506  ClearSelectionFor(characterId, Components);
507  ClearSelectionFor(characterId, InputOutputNodes);
508  ClearSelectionFor(characterId, Wires);
509  ClearSelectionFor(characterId, Labels);
510  }
511 
512  private void SelectLabelsInternal(IReadOnlyCollection<ushort> ids, ushort characterId, bool overwrite)
513  {
514  if (overwrite) { ClearSelectionFor(characterId, Labels); }
515 
516  if (!ids.Any()) { return; }
517 
518  foreach (CircuitBoxLabelNode node in Labels)
519  {
520  if (!ids.Contains(node.ID)) { continue; }
521 
522  node.SetSelected(Option.Some(characterId));
523  }
524  }
525 
526  private void SelectComponentsInternal(IReadOnlyCollection<ushort> ids, ushort characterId, bool overwrite)
527  {
528  if (overwrite) { ClearSelectionFor(characterId, Components); }
529 
530  if (!ids.Any()) { return; }
531 
532  foreach (CircuitBoxComponent node in Components)
533  {
534  if (!ids.Contains(node.ID)) { continue; }
535 
536  node.SetSelected(Option.Some(characterId));
537  }
538  }
539 
540  private void UpdateSelections(ImmutableDictionary<ushort, Option<ushort>> nodeIds,
541  ImmutableDictionary<ushort, Option<ushort>> wireIds,
542  ImmutableDictionary<CircuitBoxInputOutputNode.Type, Option<ushort>> inputOutputs,
543  ImmutableDictionary<ushort, Option<ushort>> labels)
544  {
545  foreach (var wire in Wires)
546  {
547  if (!wireIds.TryGetValue(wire.ID, out var selectedBy)) { continue; }
548 
549  if (selectedBy.TryUnwrap(out var id))
550  {
551  wire.IsSelected = true;
552  wire.SelectedBy = id;
553  continue;
554  }
555 
556  wire.IsSelected = false;
557  wire.SelectedBy = 0;
558  }
559 
560  foreach (var node in Components)
561  {
562  if (!nodeIds.TryGetValue(node.ID, out var selectedBy)) { continue; }
563 
564  node.SetSelected(selectedBy);
565  }
566 
567  foreach (var node in InputOutputNodes)
568  {
569  if (!inputOutputs.TryGetValue(node.NodeType, out var selectedBy)) { continue; }
570 
571  node.SetSelected(selectedBy);
572  }
573 
574  foreach (var node in Labels)
575  {
576  if (!labels.TryGetValue(node.ID, out var selectedBy)) { continue; }
577 
578  node.SetSelected(selectedBy);
579  }
580  }
581 
582  private void SelectWiresInternal(IReadOnlyCollection<ushort> ids, ushort characterId, bool overwrite)
583  {
584  if (overwrite) { ClearSelectionFor(characterId, Wires); }
585 
586  foreach (CircuitBoxWire wire in Wires)
587  {
588  if (!ids.Contains(wire.ID)) { continue; }
589 
590  wire.SetSelected(Option.Some(characterId));
591  }
592  }
593 
594  private void SelectInputOutputInternal(IReadOnlyCollection<CircuitBoxInputOutputNode.Type> io, ushort characterId, bool overwrite)
595  {
596  if (overwrite) { ClearSelectionFor(characterId, InputOutputNodes); }
597 
598  foreach (var node in InputOutputNodes)
599  {
600  if (!io.Contains(node.NodeType)) { continue; }
601 
602  node.SetSelected(Option.Some(characterId));
603  }
604  }
605 
606  private void RemoveComponentInternal(IReadOnlyCollection<ushort> ids)
607  {
608  foreach (CircuitBoxComponent node in Components.ToImmutableArray())
609  {
610  if (!ids.Contains(node.ID)) { continue; }
611 
612  Components.Remove(node);
613  node.Remove();
614 
615  foreach (CircuitBoxWire wire in Wires.ToImmutableArray())
616  {
617  if (node.Connectors.Contains(wire.From) || node.Connectors.Contains(wire.To))
618  {
619  RemoveWireCollectionUnsafe(wire);
620  }
621  }
622  }
623  OnViewUpdateProjSpecific();
624  }
625 
626  private void RemoveWireInternal(IReadOnlyCollection<ushort> ids)
627  {
628  foreach (CircuitBoxWire wire in Wires.ToImmutableArray())
629  {
630  if (!ids.Contains(wire.ID)) { continue; }
631 
632  RemoveWireCollectionUnsafe(wire);
633  }
634 
635  OnViewUpdateProjSpecific();
636  }
637 
638  private void RemoveWireCollectionUnsafe(CircuitBoxWire wire)
639  {
640  foreach (CircuitBoxOutputConnection output in Outputs)
641  {
642  output.Connection.CircuitBoxConnections.Remove(wire.From);
643  }
644 
645  wire.From.Connection.CircuitBoxConnections.Remove(wire.To);
646 
647  if (wire.From is CircuitBoxInputConnection input)
648  {
649  input.ExternallyConnectedTo.Remove(wire.To);
650  }
651 
652  wire.To.ExternallyConnectedFrom.Remove(wire.From);
653  wire.From.ExternallyConnectedFrom.Remove(wire.To);
654 
655  wire.Remove();
656  Wires.Remove(wire);
657  }
658 
659  private void MoveNodesInternal(IReadOnlyCollection<ushort> ids,
660  IReadOnlyCollection<CircuitBoxInputOutputNode.Type> ios,
661  IReadOnlyCollection<ushort> labels,
662  Vector2 moveAmount)
663  {
664  IEnumerable<CircuitBoxComponent> nodes = Components.Where(node => ids.Contains(node.ID));
665  foreach (CircuitBoxComponent node in nodes)
666  {
667  node.Position += moveAmount;
668  }
669 
670  foreach (var label in Labels.Where(n => labels.Contains(n.ID)))
671  {
672  label.Position += moveAmount;
673  }
674 
675 
676  foreach (var io in InputOutputNodes)
677  {
678  if (!ios.Contains(io.NodeType)) { continue; }
679  io.Position += moveAmount;
680  }
681 
682  OnViewUpdateProjSpecific();
683  }
684 
685  public override bool Select(Character character)
686  => item.GetComponent<Holdable>() is not { Attached: false } && base.Select(character);
687 
688  public partial void OnViewUpdateProjSpecific();
689 
690  partial void InitProjSpecific(ContentXElement element);
691 
692  public override void ReceiveSignal(Signal signal, Connection connection)
693  {
694  foreach (var input in Inputs)
695  {
696  if (input.Connection != connection) { continue; }
697 
698  input.ReceiveSignal(signal);
699  break;
700  }
701  }
702 
703  public static bool IsRoundRunning()
704  => !Submarine.Unloading && GameMain.GameSession is { IsRunning: true };
705 
706  public static Option<CircuitBox> FindCircuitBox(ushort itemId, byte componentIndex)
707  {
708  if (!IsRoundRunning() || Entity.FindEntityByID(itemId) is not Item item) { return Option.None; }
709 
710  if (componentIndex >= item.Components.Count)
711  {
712  return Option.None;
713  }
714 
715  ItemComponent targetComponent = item.Components[componentIndex];
716  if (targetComponent is CircuitBox circuitBox)
717  {
718  return Option.Some(circuitBox);
719  }
720 
721  return Option.None;
722  }
723 
724  private ItemContainer? GetContainerOrNull(int index) => index >= 0 && index < containers.Length ? containers[index] : null;
725 
726  public void CreateRefundItemsForUsedResources(IReadOnlyCollection<ushort> ids, Character? character)
727  {
728  if (!IsInGame()) { return; }
729 
730  var prefabsToCreate = Components.Where(comp => ids.Contains(comp.ID))
731  .Select(static comp => comp.UsedResource)
732  .ToImmutableArray();
733 
734  foreach (ItemPrefab prefab in prefabsToCreate)
735  {
736  if (character?.Inventory is null)
737  {
738  Entity.Spawner?.AddItemToSpawnQueue(prefab, item.Position, item.Submarine);
739  }
740  else
741  {
742  Entity.Spawner?.AddItemToSpawnQueue(prefab, character.Inventory);
743  }
744  }
745  }
746 
747  public static ImmutableArray<Item> GetSortedCircuitBoxItemsFromPlayer(Character? character)
748  => character?.Inventory?.FindAllItems(predicate: CanItemBeAccessed, recursive: true)
749  .OrderBy(static i => i.Prefab.Identifier == Tags.FPGACircuit)
750  .ToImmutableArray() ?? ImmutableArray<Item>.Empty;
751 
752  public static bool CanItemBeAccessed(Item item) =>
753  item.ParentInventory switch
754  {
755  ItemInventory ii => ii.Container.DrawInventory,
756  _ => true
757  };
758 
759  public static Option<Item> GetApplicableResourcePlayerHas(ItemPrefab prefab, Character? character)
760  {
761  if (character is null) { return Option.None; }
762 
763  return GetApplicableResourcePlayerHas(prefab, GetSortedCircuitBoxItemsFromPlayer(character));
764  }
765 
766  public static Option<Item> GetApplicableResourcePlayerHas(ItemPrefab prefab, ImmutableArray<Item> playerItems)
767  {
768  foreach (var invItem in playerItems)
769  {
770  if (invItem.Prefab == prefab || invItem.Prefab.Identifier == Tags.FPGACircuit)
771  {
772  return Option.Some(invItem);
773  }
774  }
775 
776  return Option.None;
777  }
778 
779  public static void SpawnItem(ItemPrefab prefab, Character? user, ItemContainer? container, Action<Item> onSpawned)
780  {
781  if (container is null)
782  {
783  throw new Exception("Circuit box has no inventory");
784  }
785 
786  if (IsInGame())
787  {
788  Entity.Spawner?.AddItemToSpawnQueue(prefab, container.Inventory, onSpawned: it =>
789  {
790  AssignWifiComponentTeam(it, user);
791  onSpawned(it);
792  });
793  return;
794  }
795 
796  Item forceSpawnedItem = new Item(prefab, Vector2.Zero, null);
797  container.Inventory.TryPutItem(forceSpawnedItem, null);
798  onSpawned(forceSpawnedItem);
799  AssignWifiComponentTeam(forceSpawnedItem, user);
800 
801  static void AssignWifiComponentTeam(Item item, Character? user)
802  {
803  if (user == null) { return; }
804  foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
805  {
806  wifiComponent.TeamID = user.TeamID;
807  }
808  }
809  }
810 
811  public static void RemoveItem(Item item)
812  {
813  if (IsInGame())
814  {
815  Entity.Spawner?.AddItemToRemoveQueue(item);
816  return;
817  }
818 
819  item.Remove();
820  }
821 
822  public static bool IsInGame()
823  => Screen.Selected is not { IsEditor: true };
824 
825  public static bool IsCircuitBoxSelected(Character character)
826  => character.SelectedItem?.GetComponent<CircuitBox>() is not null;
827  }
828 }
static ItemComponent Load(ContentXElement element, Item item, bool errorMessages=true)
Interface for entities that the clients can send events to the server
Interface for entities that the server can send events to the clients