4 using System.Collections.Generic;
5 using System.Collections.Immutable;
9 using Microsoft.Xna.Framework;
15 public static readonly ImmutableHashSet<CircuitBoxOpcode> UnrealiableOpcodes
18 public ImmutableArray<CircuitBoxInputConnection> Inputs;
19 public ImmutableArray<CircuitBoxOutputConnection> Outputs;
21 public readonly List<CircuitBoxComponent> Components =
new List<CircuitBoxComponent>();
23 public readonly List<CircuitBoxInputOutputNode> InputOutputNodes =
new();
25 public readonly List<CircuitBoxLabelNode> Labels =
new();
27 public readonly List<CircuitBoxWire> Wires =
new List<CircuitBoxWire>();
29 public override bool IsActive =>
true;
32 public override bool DontTransferInventoryBetweenSubs =>
true;
35 public override bool DisallowSellingItemsFromContainer =>
true;
37 public Option<CircuitBoxConnection> FindInputOutputConnection(Identifier connectionName)
39 foreach (CircuitBoxInputConnection input
in Inputs)
41 if (input.Name != connectionName) {
continue; }
42 return Option.Some<CircuitBoxConnection>(input);
45 foreach (CircuitBoxOutputConnection output
in Outputs)
47 if (output.Name != connectionName) {
continue; }
48 return Option.Some<CircuitBoxConnection>(output);
54 public Option<CircuitBoxConnection> FindInputOutputConnection(Connection connection)
56 foreach (CircuitBoxInputConnection input
in Inputs)
58 if (input.Connection != connection) {
continue; }
59 return Option.Some<CircuitBoxConnection>(input);
62 foreach (CircuitBoxOutputConnection output
in Outputs)
64 if (output.Connection != connection) {
continue; }
65 return Option.Some<CircuitBoxConnection>(output);
71 public readonly ItemContainer[] containers;
73 private const int ComponentContainerIndex = 0,
74 WireContainerIndex = 1;
76 public ItemContainer? ComponentContainer
77 => GetContainerOrNull(ComponentContainerIndex);
80 public ItemContainer? WireContainer
81 => GetContainerOrNull(WireContainerIndex) ?? GetContainerOrNull(ComponentContainerIndex);
83 public bool IsFull => ComponentContainer?.Inventory is { } inventory && inventory.IsFull(
true);
85 [
Editable, Serialize(
false,
IsPropertySaveable.Yes, description:
"Locked circuit boxes can only be viewed and not interacted with.")]
86 public bool Locked {
get;
set; }
88 public CircuitBox(Item item, ContentXElement element) : base(item, element)
90 containers = item.GetComponents<ItemContainer>().ToArray();
91 if (containers.Length < 1)
93 DebugConsole.ThrowError(
"Circuit box must have at least one item container to function.");
96 InitProjSpecific(element);
98 var inputBuilder = ImmutableArray.CreateBuilder<CircuitBoxInputConnection>();
99 var outputBuilder = ImmutableArray.CreateBuilder<CircuitBoxOutputConnection>();
105 outputBuilder.Add(
new CircuitBoxOutputConnection(Vector2.Zero, conn,
this));
109 inputBuilder.Add(
new CircuitBoxInputConnection(Vector2.Zero, conn,
this));
113 Inputs = inputBuilder.ToImmutable();
114 Outputs = outputBuilder.ToImmutable();
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));
119 item.OnDeselect += OnDeselected;
126 private Option<ContentXElement> delayedElementToLoad;
128 public override void Load(ContentXElement componentElement,
bool usePrefabValues, IdRemap idRemap,
bool isItemSwap)
130 base.Load(componentElement, usePrefabValues, idRemap, isItemSwap);
131 if (delayedElementToLoad.IsSome()) {
return; }
132 delayedElementToLoad = Option.Some(componentElement);
135 public override void OnInventoryChanged()
136 => OnViewUpdateProjSpecific();
138 public override void Update(
float deltaTime, Camera cam)
143 if (wasInitializedByServer)
145 foreach (var w
in Wires)
147 w.EnsureWireConnected();
149 wasInitializedByServer =
false;
152 TryInitializeNodes();
155 public override void OnMapLoaded()
156 => TryInitializeNodes();
158 private void TryInitializeNodes()
160 if (!delayedElementToLoad.TryUnwrap(out var loadElement)) {
return; }
161 LoadFromXML(loadElement);
162 delayedElementToLoad = Option.None;
165 public void LoadFromXML(ContentXElement loadElement)
167 foreach (var subElement
in loadElement.Elements())
169 string elementName = subElement.Name.ToString().ToLowerInvariant();
172 case "component" when CircuitBoxComponent.TryLoadFromXML(subElement,
this).TryUnwrap(out var comp):
173 Components.Add(comp);
175 case "wire" when CircuitBoxWire.TryLoadFromXML(subElement,
this).TryUnwrap(out var wire):
179 LoadFor(CircuitBoxInputOutputNode.Type.Input, subElement);
182 LoadFor(CircuitBoxInputOutputNode.Type.Output, subElement);
185 Labels.Add(CircuitBoxLabelNode.LoadFromXML(subElement,
this));
192 if (needsServerInitialization)
194 CreateInitializationEvent();
195 needsServerInitialization =
false;
199 void LoadFor(CircuitBoxInputOutputNode.Type type, ContentXElement subElement)
201 foreach (var node
in InputOutputNodes)
203 if (node.NodeType != type) {
continue; }
205 node.Load(subElement);
211 public void CloneFrom(CircuitBox original, Dictionary<ushort, Item> clonedContainedItems)
217 foreach (var label
in original.Labels)
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);
225 for (
int ioIndex = 0; ioIndex < original.InputOutputNodes.Count; ioIndex++)
227 var origNode = original.InputOutputNodes[ioIndex];
228 var cloneNode = InputOutputNodes[ioIndex];
230 cloneNode.Position = origNode.Position;
233 if (!clonedContainedItems.Any()) {
return; }
235 foreach (var origComp
in original.Components)
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);
242 foreach (var origWire
in original.Wires)
244 Option<CircuitBoxConnection> to = CircuitBoxConnectorIdentifier.FromConnection(origWire.To).FindConnection(
this),
245 from = CircuitBoxConnectorIdentifier.FromConnection(origWire.From).FindConnection(
this);
247 if (!to.TryUnwrap(out var toConn) || !from.TryUnwrap(out var fromConn))
249 DebugConsole.ThrowError($
"Error while cloning item \"{Name}\" - failed to find a connection for a wire. ");
253 var wireItem = origWire.BackingWire.Select(w => clonedContainedItems[w.ID]);
254 var newWire =
new CircuitBoxWire(
this, origWire.ID, wireItem, fromConn, toConn, origWire.UsedItemPrefab);
259 public override XElement Save(XElement parentElement)
261 XElement componentElement = base.Save(parentElement);
263 foreach (CircuitBoxInputOutputNode node
in InputOutputNodes)
265 componentElement.Add(node.Save());
268 foreach (CircuitBoxComponent node
in Components)
270 componentElement.Add(node.Save());
273 foreach (CircuitBoxWire wire
in Wires)
275 componentElement.Add(wire.Save());
278 foreach (var label
in Labels)
280 componentElement.Add(label.Save());
283 return componentElement;
286 public partial
void OnDeselected(Character c);
288 public record
struct CreatedWire(CircuitBoxConnectorIdentifier
Start, CircuitBoxConnectorIdentifier
End, Option<
Item>
Item, ushort ID);
290 public bool Connect(CircuitBoxConnection one, CircuitBoxConnection two, Action<CreatedWire> onCreated, ItemPrefab selectedWirePrefab)
292 if (!VerifyConnection(one, two)) {
return false; }
294 ushort
id = ICircuitBoxIdentifiable.FindFreeID(Wires);
295 switch (one.IsOutput)
297 case true when !two.IsOutput:
299 CircuitBoxConnectorIdentifier start = CircuitBoxConnectorIdentifier.FromConnection(one),
300 end = CircuitBoxConnectorIdentifier.FromConnection(two);
302 if (IsExternalConnection(one) || IsExternalConnection(two))
304 CreateWireWithoutItem(one, two,
id, selectedWirePrefab);
305 onCreated(
new CreatedWire(start, end, Option.None,
id));
309 CreateWireWithItem(one, two, selectedWirePrefab,
id, i => onCreated(
new CreatedWire(start, end, Option.Some(i),
id)));
312 case false when two.IsOutput:
314 CircuitBoxConnectorIdentifier start = CircuitBoxConnectorIdentifier.FromConnection(two),
315 end = CircuitBoxConnectorIdentifier.FromConnection(one);
316 if (IsExternalConnection(one) || IsExternalConnection(two))
318 CreateWireWithoutItem(two, one,
id, selectedWirePrefab);
319 onCreated(
new CreatedWire(start, end, Option.None,
id));
323 CreateWireWithItem(two, one, selectedWirePrefab,
id, i => onCreated(
new CreatedWire(start, end, Option.Some(i),
id)));
331 private static bool VerifyConnection(CircuitBoxConnection one, CircuitBoxConnection two)
333 if (one.IsOutput == two.IsOutput || one == two) {
return false; }
335 if (one is CircuitBoxNodeConnection oneNodeConnection &&
336 two is CircuitBoxNodeConnection twoNodeConnection)
338 if (oneNodeConnection.Component == twoNodeConnection.Component)
344 if (one is CircuitBoxNodeConnection { HasAvailableSlots:
false } ||
345 two is CircuitBoxNodeConnection { HasAvailableSlots:
false })
347 return one is not CircuitBoxNodeConnection || two is not CircuitBoxNodeConnection;
353 private void AddLabelInternal(ushort
id, Color color, Vector2 pos, NetLimitedString header, NetLimitedString body)
355 var newLabel =
new CircuitBoxLabelNode(
id, color, pos,
this);
356 newLabel.EditText(header, body);
357 Labels.Add(newLabel);
358 OnViewUpdateProjSpecific();
361 private void RemoveLabelInternal(IReadOnlyCollection<ushort> ids)
363 foreach (CircuitBoxLabelNode node
in Labels.ToImmutableArray())
365 if (!ids.Contains(node.ID)) {
continue; }
368 OnViewUpdateProjSpecific();
371 private void ResizeLabelInternal(ushort
id, Vector2 pos, Vector2 size)
373 size = Vector2.Max(size, CircuitBoxLabelNode.MinSize);
374 foreach (CircuitBoxLabelNode node
in Labels)
376 if (node.ID !=
id) {
continue; }
377 node.ApplyResize(size, pos);
380 OnViewUpdateProjSpecific();
383 private void RenameConnectionLabelsInternal(CircuitBoxInputOutputNode.Type type, Dictionary<string, string> overrides)
385 foreach (var node
in InputOutputNodes)
387 if (node.NodeType != type) {
continue; }
389 node.ReplaceAllConnectionLabelOverrides(overrides);
392 OnViewUpdateProjSpecific();
395 private static bool IsExternalConnection(CircuitBoxConnection conn) => conn is (CircuitBoxInputConnection or CircuitBoxOutputConnection);
397 private void CreateWireWithoutItem(CircuitBoxConnection one, CircuitBoxConnection two, ushort
id, ItemPrefab prefab)
399 bool hasExternalConnection =
false;
400 if (one is CircuitBoxInputConnection input)
402 hasExternalConnection =
true;
403 input.ExternallyConnectedTo.Add(two);
406 if (two is CircuitBoxOutputConnection output)
408 hasExternalConnection =
true;
409 one.Connection.CircuitBoxConnections.Add(output);
412 if (hasExternalConnection)
414 two.ExternallyConnectedFrom.Add(one);
417 AddWireDirect(
id, prefab, Option.None, one, two);
420 private void CreateWireWithItem(CircuitBoxConnection one, CircuitBoxConnection two, ItemPrefab prefab, ushort wireId, Action<Item> onItemSpawned)
422 if (WireContainer is
null) {
return; }
424 if (IsExternalConnection(one) || IsExternalConnection(two))
426 DebugConsole.ThrowError(
"Cannot add a wire between an external connection and a component connection.");
430 SpawnItem(prefab, user:
null, container: WireContainer, onSpawned: wire =>
432 AddWireDirect(wireId, prefab, Option.Some(wire), one, two);
437 private void CreateWireWithItem(CircuitBoxConnection one, CircuitBoxConnection two, ushort wireId, Item it)
439 if (IsExternalConnection(one) || IsExternalConnection(two))
441 DebugConsole.ThrowError(
"Cannot add a wire between an external connection and a component connection.");
445 AddWireDirect(wireId, it.Prefab, Option.Some(it), one, two);
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));
451 private void RenameLabelInternal(ushort
id, Color color, NetLimitedString header, NetLimitedString body)
453 foreach (CircuitBoxLabelNode node
in Labels)
455 if (node.ID !=
id) {
continue; }
457 node.EditText(header, body);
463 private bool AddComponentInternal(ushort
id, ItemPrefab prefab, ItemPrefab usedResource, Vector2 pos, Character? user, Action<Item>? onItemSpawned)
465 if (
id is ICircuitBoxIdentifiable.NullComponentID)
467 DebugConsole.ThrowError(
"Unable to add component because there are no free IDs.");
471 if (ComponentContainer?.Inventory is { } inventory && inventory.HowManyCanBePut(prefab) <= 0)
473 DebugConsole.ThrowError(
"Unable to add component because there is no space in the inventory.");
477 SpawnItem(prefab, user, ComponentContainer, spawnedItem =>
479 Components.Add(
new CircuitBoxComponent(
id, spawnedItem, pos,
this, usedResource));
480 onItemSpawned?.Invoke(spawnedItem);
481 OnViewUpdateProjSpecific();
483 OnViewUpdateProjSpecific();
488 private void AddComponentInternalUnsafe(ushort
id, Item backingItem, ItemPrefab usedResource, Vector2 pos)
490 Components.Add(
new CircuitBoxComponent(
id, backingItem, pos,
this, usedResource));
491 OnViewUpdateProjSpecific();
494 private static void ClearSelectionFor(ushort characterId, IReadOnlyCollection<CircuitBoxSelectable> nodes)
496 foreach (var node
in nodes)
498 if (node.SelectedBy != characterId) {
continue; }
500 node.SetSelected(Option.None);
504 private void ClearAllSelectionsInternal(ushort characterId)
506 ClearSelectionFor(characterId, Components);
507 ClearSelectionFor(characterId, InputOutputNodes);
508 ClearSelectionFor(characterId, Wires);
509 ClearSelectionFor(characterId, Labels);
512 private void SelectLabelsInternal(IReadOnlyCollection<ushort> ids, ushort characterId,
bool overwrite)
514 if (overwrite) { ClearSelectionFor(characterId, Labels); }
516 if (!ids.Any()) {
return; }
518 foreach (CircuitBoxLabelNode node
in Labels)
520 if (!ids.Contains(node.ID)) {
continue; }
522 node.SetSelected(Option.Some(characterId));
526 private void SelectComponentsInternal(IReadOnlyCollection<ushort> ids, ushort characterId,
bool overwrite)
528 if (overwrite) { ClearSelectionFor(characterId, Components); }
530 if (!ids.Any()) {
return; }
532 foreach (CircuitBoxComponent node
in Components)
534 if (!ids.Contains(node.ID)) {
continue; }
536 node.SetSelected(Option.Some(characterId));
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)
545 foreach (var wire
in Wires)
547 if (!wireIds.TryGetValue(wire.ID, out var selectedBy)) {
continue; }
549 if (selectedBy.TryUnwrap(out var
id))
551 wire.IsSelected =
true;
552 wire.SelectedBy = id;
556 wire.IsSelected =
false;
560 foreach (var node
in Components)
562 if (!nodeIds.TryGetValue(node.ID, out var selectedBy)) {
continue; }
564 node.SetSelected(selectedBy);
567 foreach (var node
in InputOutputNodes)
569 if (!inputOutputs.TryGetValue(node.NodeType, out var selectedBy)) {
continue; }
571 node.SetSelected(selectedBy);
574 foreach (var node
in Labels)
576 if (!labels.TryGetValue(node.ID, out var selectedBy)) {
continue; }
578 node.SetSelected(selectedBy);
582 private void SelectWiresInternal(IReadOnlyCollection<ushort> ids, ushort characterId,
bool overwrite)
584 if (overwrite) { ClearSelectionFor(characterId, Wires); }
586 foreach (CircuitBoxWire wire
in Wires)
588 if (!ids.Contains(wire.ID)) {
continue; }
590 wire.SetSelected(Option.Some(characterId));
594 private void SelectInputOutputInternal(IReadOnlyCollection<CircuitBoxInputOutputNode.Type> io, ushort characterId,
bool overwrite)
596 if (overwrite) { ClearSelectionFor(characterId, InputOutputNodes); }
598 foreach (var node
in InputOutputNodes)
600 if (!io.Contains(node.NodeType)) {
continue; }
602 node.SetSelected(Option.Some(characterId));
606 private void RemoveComponentInternal(IReadOnlyCollection<ushort> ids)
608 foreach (CircuitBoxComponent node
in Components.ToImmutableArray())
610 if (!ids.Contains(node.ID)) {
continue; }
612 Components.Remove(node);
615 foreach (CircuitBoxWire wire
in Wires.ToImmutableArray())
617 if (node.Connectors.Contains(wire.From) || node.Connectors.Contains(wire.To))
619 RemoveWireCollectionUnsafe(wire);
623 OnViewUpdateProjSpecific();
626 private void RemoveWireInternal(IReadOnlyCollection<ushort> ids)
628 foreach (CircuitBoxWire wire
in Wires.ToImmutableArray())
630 if (!ids.Contains(wire.ID)) {
continue; }
632 RemoveWireCollectionUnsafe(wire);
635 OnViewUpdateProjSpecific();
638 private void RemoveWireCollectionUnsafe(CircuitBoxWire wire)
640 foreach (CircuitBoxOutputConnection output
in Outputs)
642 output.Connection.CircuitBoxConnections.Remove(wire.From);
645 wire.From.Connection.CircuitBoxConnections.Remove(wire.To);
647 if (wire.From is CircuitBoxInputConnection input)
649 input.ExternallyConnectedTo.Remove(wire.To);
652 wire.To.ExternallyConnectedFrom.Remove(wire.From);
653 wire.From.ExternallyConnectedFrom.Remove(wire.To);
659 private void MoveNodesInternal(IReadOnlyCollection<ushort> ids,
660 IReadOnlyCollection<CircuitBoxInputOutputNode.Type> ios,
661 IReadOnlyCollection<ushort> labels,
664 IEnumerable<CircuitBoxComponent> nodes = Components.Where(node => ids.Contains(node.ID));
665 foreach (CircuitBoxComponent node
in nodes)
667 node.Position += moveAmount;
670 foreach (var label
in Labels.Where(n => labels.Contains(n.ID)))
672 label.Position += moveAmount;
676 foreach (var io
in InputOutputNodes)
678 if (!ios.Contains(io.NodeType)) {
continue; }
679 io.Position += moveAmount;
682 OnViewUpdateProjSpecific();
685 public override bool Select(Character character)
686 => item.GetComponent<Holdable>() is not { Attached:
false } && base.Select(character);
688 public partial
void OnViewUpdateProjSpecific();
690 partial
void InitProjSpecific(ContentXElement element);
692 public override void ReceiveSignal(Signal signal, Connection connection)
694 foreach (var input
in Inputs)
696 if (input.Connection != connection) {
continue; }
698 input.ReceiveSignal(signal);
703 public static bool IsRoundRunning()
704 => !
Submarine.Unloading && GameMain.GameSession is { IsRunning:
true };
706 public static Option<CircuitBox> FindCircuitBox(ushort itemId,
byte componentIndex)
708 if (!IsRoundRunning() || Entity.FindEntityByID(itemId) is not Item item) {
return Option.None; }
710 if (componentIndex >= item.Components.Count)
715 ItemComponent targetComponent = item.Components[componentIndex];
716 if (targetComponent is CircuitBox circuitBox)
718 return Option.Some(circuitBox);
724 private ItemContainer? GetContainerOrNull(
int index) => index >= 0 && index < containers.Length ? containers[index] :
null;
726 public void CreateRefundItemsForUsedResources(IReadOnlyCollection<ushort> ids, Character? character)
728 if (!IsInGame()) {
return; }
730 var prefabsToCreate = Components.Where(comp => ids.Contains(comp.ID))
731 .Select(
static comp => comp.UsedResource)
734 foreach (ItemPrefab prefab
in prefabsToCreate)
736 if (character?.Inventory is
null)
738 Entity.Spawner?.AddItemToSpawnQueue(prefab, item.Position, item.Submarine);
742 Entity.Spawner?.AddItemToSpawnQueue(prefab, character.Inventory);
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;
752 public static bool CanItemBeAccessed(Item item) =>
753 item.ParentInventory
switch
755 ItemInventory ii => ii.Container.DrawInventory,
759 public static Option<Item> GetApplicableResourcePlayerHas(ItemPrefab prefab, Character? character)
761 if (character is
null) {
return Option.None; }
763 return GetApplicableResourcePlayerHas(prefab, GetSortedCircuitBoxItemsFromPlayer(character));
766 public static Option<Item> GetApplicableResourcePlayerHas(ItemPrefab prefab, ImmutableArray<Item> playerItems)
768 foreach (var invItem
in playerItems)
770 if (invItem.Prefab == prefab || invItem.Prefab.Identifier == Tags.FPGACircuit)
772 return Option.Some(invItem);
779 public static void SpawnItem(ItemPrefab prefab, Character? user, ItemContainer? container, Action<Item> onSpawned)
781 if (container is
null)
783 throw new Exception(
"Circuit box has no inventory");
788 Entity.Spawner?.AddItemToSpawnQueue(prefab, container.Inventory, onSpawned: it =>
790 AssignWifiComponentTeam(it, user);
796 Item forceSpawnedItem =
new Item(prefab, Vector2.Zero,
null);
797 container.Inventory.TryPutItem(forceSpawnedItem,
null);
798 onSpawned(forceSpawnedItem);
799 AssignWifiComponentTeam(forceSpawnedItem, user);
801 static void AssignWifiComponentTeam(Item item, Character? user)
803 if (user ==
null) {
return; }
804 foreach (WifiComponent wifiComponent
in item.GetComponents<WifiComponent>())
806 wifiComponent.TeamID = user.TeamID;
811 public static void RemoveItem(Item item)
815 Entity.Spawner?.AddItemToRemoveQueue(item);
822 public static bool IsInGame()
823 => Screen.Selected is not { IsEditor:
true };
825 public static bool IsCircuitBoxSelected(Character character)
826 => character.SelectedItem?.GetComponent<CircuitBox>() is not
null;
List< Connection > Connections
ItemComponent(Item item, ContentXElement element)
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