Unstable 1.1.14.0

This commit is contained in:
Markus Isberg
2023-10-02 16:43:54 +03:00
parent 94f5a93a0c
commit cf8f0de659
606 changed files with 21906 additions and 11456 deletions
@@ -0,0 +1,84 @@
#nullable enable
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
internal partial class CircuitBoxComponent : CircuitBoxNode, ICircuitBoxIdentifiable
{
public readonly Item Item;
public ushort ID { get; }
public readonly ItemPrefab UsedResource;
public CircuitBoxComponent(ushort id, Item item, Vector2 position, CircuitBox circuitBox, ItemPrefab usedResource): base(circuitBox)
{
if (item.Connections is null)
{
throw new ArgumentNullException(nameof(item.Connections), $"Tried to load a CircuitBoxNode with an item \"{item.Prefab.Name}\" that has no connections.");
}
var conns = item.Connections.Select(connection => new CircuitBoxNodeConnection(Vector2.Zero, this, connection, circuitBox)).ToList();
Vector2 size = CalculateSize(conns);
ID = id;
Item = item;
Size = size;
Connectors = conns.Cast<CircuitBoxConnection>().ToImmutableArray();
Position = position;
UsedResource = usedResource;
UpdatePositions();
}
public static Option<CircuitBoxComponent> TryLoadFromXML(ContentXElement element, CircuitBox circuitBox)
{
ushort id = element.GetAttributeUInt16("id", ICircuitBoxIdentifiable.NullComponentID);
Vector2 position = element.GetAttributeVector2("position", Vector2.Zero);
var itemIdOption = ItemSlotIndexPair.TryDeserializeFromXML(element, "backingitemid");
Identifier usedResourceIdentifier = element.GetAttributeIdentifier("usedresource", Identifier.Empty);
if (!itemIdOption.TryUnwrap(out var itemId) || itemId.FindItemInContainer(circuitBox.ComponentContainer) is not { } backingItem)
{
DebugConsole.ThrowErrorAndLogToGA("CircuitBoxComponent.TryLoadFromXML:IdNotFound",
$"Failed to find item with ID {itemId} for CircuitBoxNode with ID {id}");
return Option.None;
}
if (!ItemPrefab.Prefabs.TryGet(usedResourceIdentifier, out var usedResource))
{
DebugConsole.ThrowErrorAndLogToGA("CircuitBoxComponent.TryLoadXML:UsedResourceNotFound",
$"Failed to find item prefab with identifier {usedResourceIdentifier} for CircuitBoxNode with ID {id}");
return Option.None;
}
return Option.Some(new CircuitBoxComponent(id, backingItem, position, circuitBox, usedResource));
}
public XElement Save()
{
return new XElement("Component",
new XAttribute("id", ID),
new XAttribute("position", XMLExtensions.Vector2ToString(Position)),
new XAttribute("backingitemid", ItemSlotIndexPair.Serialize(Item)),
new XAttribute("usedresource", UsedResource.Identifier));
}
public void Remove()
{
if (Entity.Spawner is { } spawner && Screen.Selected is not { IsEditor: true })
{
spawner.AddEntityToRemoveQueue(Item);
return;
}
// if EntitySpawner is not available
Item.Remove();
}
}
}
@@ -0,0 +1,121 @@
#nullable enable
using System.Collections.Generic;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
internal class CircuitBoxInputConnection : CircuitBoxConnection
{
/// <summary>
/// Circuit box input connection is classified as an output because it behaves like an output inside the circuit box.
/// As in you can connect it to a input pin of a component.
/// </summary>
public override bool IsOutput => true;
public readonly List<CircuitBoxConnection> ExternallyConnectedTo = new();
public CircuitBoxInputConnection(Vector2 position, Connection connection, CircuitBox box) : base(position, connection, box) { }
public override void ReceiveSignal(Signal signal)
{
foreach (CircuitBoxConnection connector in ExternallyConnectedTo)
{
if (connector is CircuitBoxOutputConnection output)
{
output.ReceiveSignal(signal);
return;
}
Connection.SendSignalIntoConnection(signal, connector.Connection);
}
}
}
internal class CircuitBoxOutputConnection : CircuitBoxConnection
{
/// <summary>
/// Circuit box output connection is classified as an input because it behaves like an input inside the circuit box.
/// As in you can connect it to a output pin of a component.
/// </summary>
public override bool IsOutput => false;
public CircuitBoxOutputConnection(Vector2 position, Connection connection, CircuitBox circuitBox) : base(position, connection, circuitBox) { }
public override void ReceiveSignal(Signal signal) => CircuitBox.Item.SendSignal(signal, Connection);
}
internal class CircuitBoxNodeConnection : CircuitBoxConnection
{
public override bool IsOutput => Connection.IsOutput;
public CircuitBoxComponent Component;
public bool HasAvailableSlots => Connection.WireSlotsAvailable();
public CircuitBoxNodeConnection(Vector2 position, CircuitBoxComponent component, Connection connection, CircuitBox circuitBox) : base(position, connection, circuitBox)
{
Component = component;
}
public override void ReceiveSignal(Signal signal) => Connection.SendSignalIntoConnection(signal, Connection);
}
internal abstract partial class CircuitBoxConnection
{
public readonly Connection Connection;
public abstract bool IsOutput { get; }
public RectangleF Rect;
private Vector2 position;
public List<CircuitBoxConnection> ExternallyConnectedFrom = new();
public static float Size = CircuitBoxSizes.ConnectorSize;
public Vector2 Position
{
get => position;
set
{
Rect.X = value.X - Rect.Width / 2f;
Rect.Y = value.Y - Rect.Height / 2f;
position = value;
}
}
public float Length { get; private set; }
public Vector2 AnchorPoint
=> new Vector2(IsOutput ? Rect.Right + CircuitBoxSizes.AnchorOffset : Rect.Left - CircuitBoxSizes.AnchorOffset, Rect.Center.Y);
public readonly CircuitBox CircuitBox;
protected CircuitBoxConnection(Vector2 position, Connection connection, CircuitBox circuitBox)
{
Connection = connection;
Rect.Width = Rect.Height = Size;
Position = position;
CircuitBox = circuitBox;
InitProjSpecific(circuitBox);
}
private partial void InitProjSpecific(CircuitBox circuitBox);
public abstract void ReceiveSignal(Signal signal);
public bool Contains(Vector2 pos)
{
float x = Rect.X,
y = -(Rect.Y + Rect.Height),
width = Rect.Width,
height = Rect.Height;
RectangleF rect = new(x, y, width, height);
return rect.Contains(pos);
}
}
}
@@ -0,0 +1,109 @@
#nullable enable
using System;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
[NetworkSerialize]
internal readonly record struct NetCircuitBoxCursorInfo(Vector2[] RecordedPositions, Option<Vector2> DragStart, Option<Identifier> HeldItem, ushort CharacterID = 0) : INetSerializableStruct;
internal sealed class CircuitBoxCursor
{
public NetCircuitBoxCursorInfo Info;
public CircuitBoxCursor(NetCircuitBoxCursorInfo info)
{
if (Entity.FindEntityByID(info.CharacterID) is Character c)
{
Color = GenerateColor(c.Name);
}
UpdateInfo(info);
}
public void UpdateInfo(NetCircuitBoxCursorInfo newInfo)
{
Info = newInfo;
newInfo.HeldItem.Match(
some: newIdentifier =>
HeldPrefab.Match(
some: oldPrefab =>
{
if (oldPrefab.Identifier == newIdentifier) { return; }
SetHeldPrefab(newIdentifier);
},
none: () => SetHeldPrefab(newIdentifier)
),
none: () => HeldPrefab = Option.None);
prevPosition = DrawPosition;
void SetHeldPrefab(Identifier identifier)
{
ItemPrefab? prefab = ItemPrefab.Prefabs.Find(prefab => prefab.Identifier.Equals(identifier));
HeldPrefab = prefab is null ? Option.None : Option.Some(prefab);
}
}
public Option<ItemPrefab> HeldPrefab { get; private set; } = Option.None;
public Color Color = Color.White;
public static Color GenerateColor(string name)
{
Random random = new Random(ToolBox.StringToInt(name));
return ToolBox.HSVToRGB(random.NextSingle() * 360f, 1f, 1f);
}
private const float UpdateTimeout = 5f;
private float updateTimer;
private float positionTimer;
private Vector2 prevPosition;
public Vector2 DrawPosition;
public bool IsActive => updateTimer < UpdateTimeout;
public void Update(float deltaTime)
{
updateTimer += deltaTime;
Vector2 finalPosition = Info.RecordedPositions[^1];
if (positionTimer > 1f)
{
DrawPosition = finalPosition;
prevPosition = Vector2.Zero;
}
else
{
positionTimer += deltaTime;
float stepTimer = positionTimer * 10f;
int targetPositonIndex = (int)MathF.Floor(stepTimer);
int prevPosIndex = targetPositonIndex - 1;
Vector2 targetPosition = IsInRange(targetPositonIndex, Info.RecordedPositions.Length)
? Info.RecordedPositions[targetPositonIndex]
: finalPosition;
Vector2 prevTargetPosition = IsInRange(prevPosIndex, Info.RecordedPositions.Length)
? Info.RecordedPositions[prevPosIndex]
: prevPosition;
DrawPosition = Vector2.Lerp(prevTargetPosition, targetPosition, MathHelper.Clamp(stepTimer % 1f, 0f, 1f));
}
static bool IsInRange(int index, int length)
=> index >= 0 && index < length;
}
public void ResetTimers()
{
positionTimer = 0f;
updateTimer = 0f;
}
}
}
@@ -0,0 +1,38 @@
#nullable enable
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
internal sealed class CircuitBoxInputOutputNode : CircuitBoxNode
{
public enum Type
{
Invalid,
Input,
Output
}
public Type NodeType;
public CircuitBoxInputOutputNode(IReadOnlyList<CircuitBoxConnection> conns, Vector2 initialPosition, Type type, CircuitBox circuitBox): base(circuitBox)
{
Size = CalculateSize(conns);
Connectors = conns.ToImmutableArray();
Position = initialPosition;
NodeType = type;
UpdatePositions();
}
public XElement Save() => new XElement($"{NodeType}Node", new XAttribute("pos", XMLExtensions.Vector2ToString(Position)));
public void Load(ContentXElement element)
{
Position = element.GetAttributeVector2("pos", Vector2.Zero);
}
}
}
@@ -0,0 +1,163 @@
#nullable enable
using System;
using System.Collections.Immutable;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
public enum CircuitBoxOpcode
{
Error,
Cursor,
AddComponent,
MoveComponent,
AddWire,
RemoveWire,
SelectComponents,
SelectWires,
UpdateSelection,
DeleteComponent,
ServerInitialize
}
[NetworkSerialize]
internal readonly record struct NetCircuitBoxHeader(CircuitBoxOpcode Opcode, ushort ItemID, byte ComponentIndex) : INetSerializableStruct
{
public Option<CircuitBox> FindTarget() => CircuitBox.FindCircuitBox(ItemID, ComponentIndex);
}
[NetworkSerialize]
internal readonly record struct CircuitBoxConnectorIdentifier(Identifier SignalConnection, Option<ushort> TargetId) : INetSerializableStruct
{
public static CircuitBoxConnectorIdentifier FromConnection(CircuitBoxConnection connection) =>
connection switch
{
(CircuitBoxInputConnection or CircuitBoxOutputConnection)
=> new CircuitBoxConnectorIdentifier(connection.Name.ToIdentifier(), Option.None),
CircuitBoxNodeConnection nodeConnection
=> new CircuitBoxConnectorIdentifier(connection.Name.ToIdentifier(), Option.Some(nodeConnection.Component.ID)),
_ => throw new ArgumentOutOfRangeException(nameof(connection))
};
public Option<CircuitBoxConnection> FindConnection(CircuitBox circuitBox)
{
if (!TargetId.TryUnwrap(out var id))
{
return circuitBox.FindInputOutputConnection(SignalConnection);
}
foreach (CircuitBoxComponent boxNode in circuitBox.Components)
{
if (boxNode.ID != id) { continue; }
foreach (var conn in boxNode.Connectors)
{
if (conn.Name != SignalConnection) { continue; }
return Option.Some(conn);
}
}
return Option.None;
}
public XElement Save(string name) => new XElement(name,
new XAttribute("name", SignalConnection),
new XAttribute("target", TargetId.TryUnwrap(out var value) ? value.ToString() : string.Empty));
public static CircuitBoxConnectorIdentifier Load(ContentXElement element)
{
string? name = element.GetAttributeString("name", string.Empty);
string? target = element.GetAttributeString("target", string.Empty);
Option<ushort> targetId = Option.None;
if (!string.IsNullOrWhiteSpace(target))
{
targetId = ushort.TryParse(target, out var value) ? Option.Some(value) : Option.None;
}
return new CircuitBoxConnectorIdentifier(name.ToIdentifier(), targetId);
}
public override string ToString()
=> $"{{Name: {SignalConnection}, ID: {(TargetId.TryUnwrap(out var value) ? value.ToString() : "N/A")}}}";
}
[NetworkSerialize]
internal readonly record struct CircuitBoxAddComponentEvent(UInt32 PrefabIdentifier, Vector2 Position) : INetSerializableStruct;
[NetworkSerialize]
internal readonly record struct CircuitBoxServerCreateComponentEvent(ushort BackingItemId, UInt32 UsedResource, ushort ComponentId, Vector2 Position) : INetSerializableStruct;
[NetworkSerialize]
internal readonly record struct CircuitBoxRemoveComponentEvent(ImmutableArray<ushort> TargetIDs) : INetSerializableStruct;
[NetworkSerialize]
internal readonly record struct CircuitBoxMoveComponentEvent(ImmutableArray<ushort> TargetIDs, ImmutableArray<CircuitBoxInputOutputNode.Type> IOs, Vector2 MoveAmount) : INetSerializableStruct;
[NetworkSerialize]
internal readonly record struct CircuitBoxSelectNodesEvent(ImmutableArray<ushort> TargetIDs, ImmutableArray<CircuitBoxInputOutputNode.Type> IOs, bool Overwrite, ushort CharacterID) : INetSerializableStruct;
[NetworkSerialize]
internal readonly record struct CircuitBoxServerUpdateSelection(ImmutableArray<CircuitBoxIdSelectionPair> ComponentIds, ImmutableArray<CircuitBoxIdSelectionPair> WireIds, ImmutableArray<CircuitBoxTypeSelectionPair> InputOutputs) : INetSerializableStruct;
[NetworkSerialize]
internal readonly record struct CircuitBoxIdSelectionPair(ushort ID, Option<ushort> SelectedBy) : INetSerializableStruct;
[NetworkSerialize]
internal readonly record struct CircuitBoxTypeSelectionPair(CircuitBoxInputOutputNode.Type Type, Option<ushort> SelectedBy) : INetSerializableStruct;
[NetworkSerialize]
internal readonly record struct CircuitBoxSelectWiresEvent(ImmutableArray<ushort> TargetIDs, bool Overwrite, ushort CharacterID) : INetSerializableStruct;
[NetworkSerialize]
internal readonly record struct CircuitBoxClientAddWireEvent(Color Color, CircuitBoxConnectorIdentifier Start, CircuitBoxConnectorIdentifier End, UInt32 SelectedWirePrefabIdentifier) : INetSerializableStruct;
[NetworkSerialize]
internal readonly record struct CircuitBoxServerCreateWireEvent(CircuitBoxClientAddWireEvent Request, ushort WireId, Option<ushort> BackingItemId) : INetSerializableStruct;
[NetworkSerialize]
internal readonly record struct CircuitBoxRemoveWireEvent(ImmutableArray<ushort> TargetIDs) : INetSerializableStruct;
[NetworkSerialize]
internal readonly record struct CircuitBoxErrorEvent(string Message) : INetSerializableStruct;
[NetworkSerialize]
internal readonly record struct CircuitBoxInitializeStateFromServerEvent(
ImmutableArray<CircuitBoxServerCreateComponentEvent> Components,
ImmutableArray<CircuitBoxServerCreateWireEvent> Wires,
Vector2 InputPos,
Vector2 OutputPos) : INetSerializableStruct;
internal readonly record struct CircuitBoxEventData(INetSerializableStruct Data) : ItemComponent.IEventData
{
public CircuitBoxOpcode Opcode =>
Data switch
{
(CircuitBoxAddComponentEvent or CircuitBoxServerCreateComponentEvent)
=> CircuitBoxOpcode.AddComponent,
CircuitBoxRemoveComponentEvent
=> CircuitBoxOpcode.DeleteComponent,
CircuitBoxMoveComponentEvent
=> CircuitBoxOpcode.MoveComponent,
CircuitBoxSelectNodesEvent
=> CircuitBoxOpcode.SelectComponents,
CircuitBoxSelectWiresEvent
=> CircuitBoxOpcode.SelectWires,
CircuitBoxServerUpdateSelection
=> CircuitBoxOpcode.UpdateSelection,
(CircuitBoxClientAddWireEvent or CircuitBoxServerCreateWireEvent)
=> CircuitBoxOpcode.AddWire,
CircuitBoxRemoveWireEvent
=> CircuitBoxOpcode.RemoveWire,
CircuitBoxInitializeStateFromServerEvent
=> CircuitBoxOpcode.ServerInitialize,
_ => throw new ArgumentOutOfRangeException(nameof(Data))
};
}
}
@@ -0,0 +1,132 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
internal partial class CircuitBoxNode : CircuitBoxSelectable
{
public Vector2 Size;
public RectangleF Rect;
private Vector2 position;
public Vector2 Position
{
get => position;
set
{
const float clampSize = CircuitBoxSizes.PlayableAreaSize / 2f;
position = new Vector2(Math.Clamp(value.X, -clampSize, clampSize),
Math.Clamp(value.Y, -clampSize, clampSize));
UpdatePositions();
}
}
public ImmutableArray<CircuitBoxConnection> Connectors;
public static float Opacity = 0.8f;
public readonly CircuitBox CircuitBox;
public CircuitBoxNode(CircuitBox circuitBox)
{
CircuitBox = circuitBox;
}
public static Vector2 CalculateSize(IReadOnlyList<CircuitBoxConnection> conns)
{
Vector2 leftSize = Vector2.Zero,
rightSize = Vector2.Zero;
foreach (var c in conns)
{
if (c.IsOutput)
{
rightSize.X = MathF.Max(rightSize.X, c.Length);
}
else
{
leftSize.X = MathF.Max(leftSize.X, c.Length);
}
if (c.IsOutput)
{
rightSize.Y += CircuitBoxConnection.Size;
}
else
{
leftSize.Y += CircuitBoxConnection.Size;
}
}
return new Vector2(leftSize.X + CircuitBoxSizes.NodeInitialPadding + rightSize.X, CircuitBoxSizes.NodeInitialPadding + MathF.Max(leftSize.Y, rightSize.Y));
}
protected void UpdatePositions()
{
Vector2 rectStart = Position - Size / 2f;
Vector2 rectSize = Size;
rectSize.Y += CircuitBoxSizes.NodeHeaderHeight;
Rect = new RectangleF(rectStart, rectSize);
#if CLIENT
UpdateDrawRects();
#endif
int leftIndex = 0,
rightIndex = 0;
int inputCount = 0,
outputCount = 0;
foreach (var c in Connectors)
{
if (c.IsOutput)
{
outputCount++;
}
else
{
inputCount++;
}
}
Vector2 drawPos = Position;
drawPos.Y = -drawPos.Y;
foreach (var c in Connectors)
{
bool isOutput = c.IsOutput;
int yIndex = isOutput ? rightIndex : leftIndex;
int count = isOutput ? outputCount : inputCount;
float totalHeight = (count * CircuitBoxConnection.Size) / 2f;
float y = (yIndex * CircuitBoxConnection.Size) - totalHeight;
float halfWidth = Rect.Width / 2f - CircuitBoxConnection.Size / 2f;
halfWidth -= 16f;
float xOffset = c.IsOutput ? halfWidth : -halfWidth;
Vector2 inputPos = drawPos + new Vector2(xOffset, y + c.Rect.Height / 2f);
c.Position = inputPos;
if (isOutput)
{
rightIndex++;
}
else
{
leftIndex++;
}
}
}
}
}
@@ -0,0 +1,43 @@
#nullable enable
using System;
namespace Barotrauma
{
internal class CircuitBoxSelectable
{
public bool IsSelected;
public ushort SelectedBy;
public bool IsSelectedByMe
{
get
{
if (GameMain.NetworkMember is { IsServer: true })
{
throw new Exception("CircuitBoxSelectable.IsSelectedByMe should never be used by the server.");
}
if (Character.Controlled is { } controlled)
{
return SelectedBy == controlled.ID;
}
return false;
}
}
public void SetSelected(Option<ushort> selectedBy)
{
if (selectedBy.TryUnwrap(out ushort id))
{
SelectedBy = id;
IsSelected = true;
return;
}
IsSelected = false;
SelectedBy = 0;
}
}
}
@@ -0,0 +1,17 @@
#nullable enable
namespace Barotrauma
{
internal static class CircuitBoxSizes
{
public const int ConnectorSize = 32;
public const int AnchorOffset = 24;
public const int NodeHeaderHeight = 48;
public const int NodeInitialPadding = 64;
public const int WireWidth = 10;
public const int WireKnobLength = 16;
public const int NodeHeaderTextPadding = 8;
public const float PlayableAreaSize = 8192f;
}
}
@@ -0,0 +1,186 @@
#nullable enable
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
internal partial class CircuitBoxWire : CircuitBoxSelectable, ICircuitBoxIdentifiable
{
public CircuitBoxConnection From, To;
public readonly Option<Item> BackingWire;
public readonly Color Color;
public readonly ItemPrefab UsedItemPrefab;
public ushort ID { get; }
public CircuitBoxWire(CircuitBox circuitBox, ushort Id, Option<Item> backingItem, CircuitBoxConnection from, CircuitBoxConnection to, ItemPrefab prefab)
{
ID = Id;
From = from;
To = to;
BackingWire = backingItem;
Color = prefab.SpriteColor;
UsedItemPrefab = prefab;
#if CLIENT
Renderer = new CircuitBoxWireRenderer(Option.Some(this), to.AnchorPoint, from.AnchorPoint, Color, circuitBox.WireSprite);
#endif
EnsureWireConnected();
}
public XElement Save()
{
XElement element = new XElement("Wire",
new XAttribute("id", ID),
new XAttribute("backingitemid", BackingWire.TryUnwrap(out var item) ? ItemSlotIndexPair.Serialize(item) : string.Empty),
new XAttribute("prefab", UsedItemPrefab.Identifier));
XElement fromElement = CircuitBoxConnectorIdentifier.FromConnection(From).Save("From"),
toElement = CircuitBoxConnectorIdentifier.FromConnection(To).Save("To");
element.Add(fromElement);
element.Add(toElement);
return element;
}
public static Option<CircuitBoxWire> TryLoadFromXML(ContentXElement element, CircuitBox circuitBox)
{
ushort id = element.GetAttributeUInt16("id", ICircuitBoxIdentifiable.NullComponentID);
var backingItemIdOption = ItemSlotIndexPair.TryDeserializeFromXML(element, "backingitemid");
Identifier usedPrefabIdentifier = element.GetAttributeIdentifier("prefab", Identifier.Empty);
if (!ItemPrefab.Prefabs.TryGet(usedPrefabIdentifier, out var itemPrefab))
{
DebugConsole.ThrowErrorAndLogToGA("CircuitBoxWire.TryLoadFromXML:PrefabNotFound",
$"Failed to find prefab used to create wire with identifier {usedPrefabIdentifier} for CircuitBoxWire with ID {id}");
return Option.None;
}
Option<Item> backingItem = Option.None;
if (backingItemIdOption.TryUnwrap(out var backingItemIdPair))
{
if (backingItemIdPair.FindItemInContainer(circuitBox.WireContainer) is { } item)
{
backingItem = Option.Some(item);
}
else
{
DebugConsole.ThrowErrorAndLogToGA("CircuitBoxWire.TryLoadFromXML:IdNotFound",
$"Failed to find item with ID {backingItemIdPair} for CircuitBoxWire with ID {id}");
return Option.None;
}
}
Option<CircuitBoxConnection> From = Option.None,
To = Option.None;
foreach (ContentXElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "from":
var fromIdentifier = CircuitBoxConnectorIdentifier.Load(subElement);
if (fromIdentifier.FindConnection(circuitBox).TryUnwrap(out var fromConnection))
{
From = Option.Some(fromConnection);
}
break;
case "to":
var toIdentifier = CircuitBoxConnectorIdentifier.Load(subElement);
if (toIdentifier.FindConnection(circuitBox).TryUnwrap(out var toConnection))
{
To = Option.Some(toConnection);
}
break;
}
}
if (From.TryUnwrap(out var from) && To.TryUnwrap(out var to))
{
return Option.Some(new CircuitBoxWire(circuitBox, id, backingItem, from, to, itemPrefab));
}
DebugConsole.ThrowErrorAndLogToGA("CircuitBoxWire.TryLoadFromXML:MissingFromOrTo",
$"Failed to load CircuitBoxWire with ID {id}, missing \"From\" or \"To\" connection.");
return Option.None;
}
public void EnsureWireConnected()
{
EnsureExternalConnection(From, To);
EnsureExternalConnection(To, From);
if (!BackingWire.TryUnwrap(out var item) || item.GetComponent<Wire>() is not { } wire) { return; }
wire.DropOnConnect = false;
From.Connection.ConnectWire(wire);
To.Connection.ConnectWire(wire);
wire.Connect(From.Connection, 0, addNode: false, sendNetworkEvent: false);
wire.Connect(To.Connection, 1, addNode: false, sendNetworkEvent: false);
static void EnsureExternalConnection(CircuitBoxConnection one, CircuitBoxConnection two)
{
switch (one)
{
case CircuitBoxInputConnection input:
{
if (input.ExternallyConnectedTo.Contains(two)) { break; }
input.ExternallyConnectedTo.Add(two);
break;
}
case CircuitBoxOutputConnection output:
{
if (output.ExternallyConnectedFrom.Contains(two)) { break; }
output.ExternallyConnectedFrom.Add(two);
break;
}
case CircuitBoxNodeConnection node when two is CircuitBoxOutputConnection output:
{
if (node.Connection.CircuitBoxConnections.Contains(output)) { break; }
node.Connection.CircuitBoxConnections.Add(output);
break;
}
case CircuitBoxNodeConnection node when two is CircuitBoxInputConnection input:
{
if (node.ExternallyConnectedFrom.Contains(input)) { break; }
node.ExternallyConnectedFrom.Add(input);
break;
}
}
}
}
public void Remove()
{
// client should not remove wires
if (GameMain.NetworkMember is { IsClient: true }) { return; }
if (!BackingWire.TryUnwrap(out var wireItem)) { return; }
if (Entity.Spawner is { } spawner && Screen.Selected is not { IsEditor: true })
{
spawner.AddEntityToRemoveQueue(wireItem);
return;
}
Wire? wire = wireItem.GetComponent<Wire>();
if (wire is not null)
{
From.Connection.DisconnectWire(wire);
To.Connection.DisconnectWire(wire);
}
// if EntitySpawner is not available
wireItem.Remove();
}
public static ItemPrefab DefaultWirePrefab => ItemPrefab.Prefabs[Tags.RedWire];
public static ItemPrefab SelectedWirePrefab = DefaultWirePrefab;
public static readonly Color DefaultWireColor = DefaultWirePrefab.SpriteColor;
}
}
@@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma
{
public interface ICircuitBoxIdentifiable
{
public const ushort NullComponentID = ushort.MaxValue;
public ushort ID { get; }
public static ushort FindFreeID<T>(IReadOnlyCollection<T> ids) where T : ICircuitBoxIdentifiable
{
var sortedIds = ids.Select(static i => i.ID).ToImmutableHashSet();
for (ushort i = 0; i < NullComponentID - 1; i++)
{
if (!sortedIds.Contains(i))
{
return i;
}
}
return NullComponentID;
}
}
}
@@ -0,0 +1,44 @@
#nullable enable
using System;
using System.Linq;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
internal readonly record struct ItemSlotIndexPair(int Slot, int StackIndex)
{
public static Option<ItemSlotIndexPair> TryDeserializeFromXML(ContentXElement element, string elementName)
{
string? elementStr = element.GetAttributeString(elementName, string.Empty);
if (string.IsNullOrEmpty(elementStr)) { return Option.None; }
var point = XMLExtensions.ParsePoint(elementStr);
return Option.Some(new ItemSlotIndexPair(point.X, point.Y));
}
public static string Serialize(Item item)
{
Inventory parent = item.ParentInventory;
if (item.ParentInventory is null)
{
throw new Exception($"Item \"{item.Name}\" is not in an inventory.");
}
int slotIndex = parent.FindIndex(item);
int stackIndex = parent.GetItemStackSlotIndex(item, slotIndex);
if (slotIndex < 0 || stackIndex < 0)
{
throw new Exception($"Unable to find item \"{item.Name}\" in its parent inventory.");
}
return XMLExtensions.PointToString(new Point(slotIndex, stackIndex));
}
public Item? FindItemInContainer(ItemContainer? container)
=> container?.Inventory.GetItemsAt(Slot).ElementAt(StackIndex);
}
}