Replaced static lists and dictionaries with thread-safe ConcurrentDictionary or ThreadLocal collections for various item components and systems. Updated all relevant code to use snapshots (ToArray, ToList) for safe iteration, and added helper methods for marking and clearing changed connections. These changes improve thread safety and prevent potential concurrency issues in multi-threaded scenarios.
912 lines
32 KiB
C#
912 lines
32 KiB
C#
using Barotrauma.Networking;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Xml.Linq;
|
|
#if CLIENT
|
|
using Microsoft.Xna.Framework.Input;
|
|
#endif
|
|
|
|
namespace Barotrauma.Items.Components
|
|
{
|
|
partial class Wire : ItemComponent, IDrawableComponent, IServerSerializable, IClientSerializable
|
|
{
|
|
public partial class WireSection
|
|
{
|
|
private Vector2 start;
|
|
private Vector2 end;
|
|
|
|
private readonly float angle;
|
|
public readonly float Length;
|
|
|
|
public Vector2 Start
|
|
{
|
|
get { return start; }
|
|
}
|
|
public Vector2 End
|
|
{
|
|
get { return end; }
|
|
}
|
|
|
|
public WireSection(Vector2 start, Vector2 end)
|
|
{
|
|
this.start = start;
|
|
this.end = end;
|
|
|
|
angle = MathUtils.VectorToAngle(end - start);
|
|
Length = Vector2.Distance(start, end);
|
|
}
|
|
}
|
|
|
|
private bool shouldClearConnections = true;
|
|
|
|
const float MaxAttachDistance = 150.0f;
|
|
|
|
const float MinNodeDistance = 7.0f;
|
|
|
|
const int MaxNodeCount = 255;
|
|
const int MaxNodesPerNetworkEvent = 30;
|
|
|
|
private List<Vector2> nodes;
|
|
private readonly List<WireSection> sections;
|
|
|
|
private readonly Connection[] connections;
|
|
|
|
private bool canPlaceNode;
|
|
private Vector2 newNodePos;
|
|
|
|
private Vector2 sectionExtents;
|
|
|
|
private float currLength;
|
|
|
|
public bool Hidden;
|
|
|
|
private float editNodeDelay;
|
|
|
|
private bool locked;
|
|
|
|
public bool Locked
|
|
{
|
|
get
|
|
{
|
|
if (GameMain.NetworkMember?.ServerSettings != null && !GameMain.NetworkMember.ServerSettings.AllowRewiring) { return false; }
|
|
return locked || connections.Any(c => c != null && (c.ConnectionPanel.Locked || c.ConnectionPanel.TemporarilyLocked));
|
|
}
|
|
set { locked = value; }
|
|
}
|
|
|
|
public Connection[] Connections
|
|
{
|
|
get { return connections; }
|
|
}
|
|
|
|
public float Length { get; private set; }
|
|
|
|
[Serialize(0.3f, IsPropertySaveable.No), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f, DecimalCount = 2)]
|
|
public float Width
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(5000.0f, IsPropertySaveable.No, description: "The maximum distance the wire can extend (in pixels).")]
|
|
public float MaxLength
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(false, IsPropertySaveable.No, description: "If enabled, the wire will not be visible in connection panels outside the submarine editor.")]
|
|
public bool HiddenInGame
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Editable, Serialize(false, IsPropertySaveable.Yes, "If enabled, this wire will be ignored by the \"Lock all default wires\" setting.", alwaysUseInstanceValues: true)]
|
|
public bool NoAutoLock
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Editable, Serialize(false, IsPropertySaveable.Yes, "If enabled, this wire will use the sprite depth instead of a constant depth.")]
|
|
public bool UseSpriteDepth
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[Serialize(true, IsPropertySaveable.Yes, "If disabled, the wire will not be dropped when connecting. Used in circuit box to store the wires inside the box.")]
|
|
public bool DropOnConnect
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public Wire(Item item, ContentXElement element)
|
|
: base(item, element)
|
|
{
|
|
nodes = new List<Vector2>();
|
|
sections = new List<WireSection>();
|
|
connections = new Connection[2];
|
|
IsActive = false;
|
|
item.IsShootable = true;
|
|
|
|
InitProjSpecific(element);
|
|
}
|
|
|
|
partial void InitProjSpecific(ContentXElement element);
|
|
|
|
public Connection OtherConnection(Connection connection)
|
|
{
|
|
if (connection == connections[0]) { return connections[1]; }
|
|
if (connection == connections[1]) { return connections[0]; }
|
|
|
|
return null;
|
|
}
|
|
|
|
public bool IsConnectedTo(Item item)
|
|
{
|
|
if (connections[0] != null && connections[0].Item == item) { return true; }
|
|
return connections[1] != null && connections[1].Item == item;
|
|
}
|
|
|
|
public void RemoveConnection(Item item)
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
if (connections[i] == null || connections[i].Item != item) { continue; }
|
|
|
|
if (connections[i].Wires.Contains(this))
|
|
{
|
|
SetConnectedDirty();
|
|
|
|
connections[i].DisconnectWire(this);
|
|
}
|
|
|
|
connections[i] = null;
|
|
}
|
|
}
|
|
|
|
public void RemoveConnection(Connection connection)
|
|
{
|
|
if (connection == connections[0]) { connections[0] = null; }
|
|
if (connection == connections[1]) { connections[1] = null; }
|
|
|
|
SetConnectedDirty();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to add the given connection to this wire. Note that this only affects the wire -
|
|
/// adding the wire to the connection is done in <see cref="Connection.ConnectWire(Wire)"/>
|
|
/// </summary>
|
|
|
|
public bool TryConnect(Connection newConnection, bool addNode = true, bool sendNetworkEvent = false)
|
|
{
|
|
if (connections[0] == null)
|
|
{
|
|
return Connect(newConnection, 0, addNode, sendNetworkEvent);
|
|
}
|
|
else if (connections[1] == null)
|
|
{
|
|
return Connect(newConnection, 1, addNode, sendNetworkEvent);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Tries to add the given connection to this wire. Note that this only affects the wire -
|
|
/// adding the wire to the connection is done in <see cref="Connection.ConnectWire(Wire)"/>
|
|
/// </summary>
|
|
/// <param name="connectionIndex">Which end of the wire to add the connection to? 0 or 1.
|
|
/// Normally doesn't make a difference, but matters if we're copying/loading a wire,
|
|
/// in which case the 1st node should be located at the same item as the 1st connection.</param>
|
|
/// <returns></returns>
|
|
public bool Connect(Connection newConnection, int connectionIndex, bool addNode = true, bool sendNetworkEvent = false)
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
if (connections[i] == newConnection) { return false; }
|
|
}
|
|
|
|
if (connectionIndex < 0 || connectionIndex > 1)
|
|
{
|
|
DebugConsole.ThrowError($"Error while connecting a wire to {newConnection.Item}: {connectionIndex} is not a valid index.");
|
|
return false;
|
|
}
|
|
if (connections[connectionIndex] != null)
|
|
{
|
|
DebugConsole.ThrowError($"Error while connecting a wire to {newConnection.Item}: a wire is already connected to the index {connectionIndex}.");
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
if (connections[i] != null && connections[i].Item == newConnection.Item)
|
|
{
|
|
addNode = false;
|
|
break;
|
|
}
|
|
}
|
|
if (item.body != null) { item.Submarine = newConnection.Item.Submarine; }
|
|
|
|
newConnection.ConnectionPanel.DisconnectedWires.Remove(this);
|
|
|
|
connections[connectionIndex] = newConnection;
|
|
FixNodeEnds();
|
|
|
|
if (addNode)
|
|
{
|
|
AddNode(newConnection, connectionIndex);
|
|
}
|
|
|
|
SetConnectedDirty();
|
|
|
|
if (DropOnConnect)
|
|
{
|
|
if (connections[0] != null && connections[1] != null)
|
|
{
|
|
// Use ToArray() snapshot for thread-safe iteration
|
|
foreach (ItemComponent ic in item.Components.ToArray())
|
|
{
|
|
if (ic == this) { continue; }
|
|
|
|
ic.Drop(null);
|
|
}
|
|
|
|
item.Container?.RemoveContained(item);
|
|
if (item.body != null) { item.body.Enabled = false; }
|
|
|
|
IsActive = false;
|
|
|
|
CleanNodes();
|
|
}
|
|
}
|
|
|
|
if (item.body != null) { item.Submarine = newConnection.Item.Submarine; }
|
|
|
|
if (sendNetworkEvent)
|
|
{
|
|
#if SERVER
|
|
if (GameMain.Server != null)
|
|
{
|
|
CreateNetworkEvent();
|
|
}
|
|
#endif
|
|
//the wire is active if it's currently being wired to something (in character inventory and connected from one end)
|
|
IsActive =
|
|
item.ParentInventory is CharacterInventory &&
|
|
connections[0] == null ^ connections[1] == null;
|
|
}
|
|
|
|
Drawable = IsActive || nodes.Any();
|
|
|
|
UpdateSections();
|
|
return true;
|
|
}
|
|
|
|
private void AddNode(Connection newConnection, int selectedIndex)
|
|
{
|
|
Submarine refSub = newConnection.Item.Submarine;
|
|
if (refSub == null)
|
|
{
|
|
Structure attachTarget = Structure.GetAttachTarget(newConnection.Item.WorldPosition);
|
|
if (attachTarget == null && !(newConnection.Item.GetComponent<Holdable>()?.Attached ?? false))
|
|
{
|
|
connections[selectedIndex] = null;
|
|
return;
|
|
}
|
|
refSub = attachTarget?.Submarine;
|
|
}
|
|
|
|
Vector2 nodePos = RoundNode(newConnection.Item.Position);
|
|
if (refSub != null) { nodePos -= refSub.HiddenSubPosition; }
|
|
|
|
if (nodes.Count > 0 && nodes[0] == nodePos) { return; }
|
|
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) { return; }
|
|
|
|
//make sure we place the node at the correct end of the wire (the end that's closest to the new node pos)
|
|
int newNodeIndex = 0;
|
|
if (nodes.Count > 1)
|
|
{
|
|
if (connections[0] != null && connections[0] != newConnection)
|
|
{
|
|
if (Vector2.DistanceSquared(nodes[0], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
|
|
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
|
|
{
|
|
newNodeIndex = nodes.Count;
|
|
}
|
|
}
|
|
else if (connections[1] != null && connections[1] != newConnection)
|
|
{
|
|
if (Vector2.DistanceSquared(nodes[0], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
|
|
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
|
|
{
|
|
newNodeIndex = nodes.Count;
|
|
}
|
|
}
|
|
else if (Vector2.DistanceSquared(nodes[nodes.Count - 1], nodePos) < Vector2.DistanceSquared(nodes[0], nodePos))
|
|
{
|
|
newNodeIndex = nodes.Count;
|
|
}
|
|
}
|
|
|
|
if (newNodeIndex == 0 && nodes.Count > 1)
|
|
{
|
|
nodes.Insert(0, nodePos);
|
|
}
|
|
else
|
|
{
|
|
nodes.Add(nodePos);
|
|
}
|
|
}
|
|
|
|
public override void Equip(Character character)
|
|
{
|
|
if (shouldClearConnections) { ClearConnections(character); }
|
|
IsActive = true;
|
|
}
|
|
|
|
public override void Unequip(Character character)
|
|
{
|
|
ClearConnections(character);
|
|
IsActive = false;
|
|
}
|
|
|
|
public override void Drop(Character dropper, bool setTransform = true)
|
|
{
|
|
if (shouldClearConnections) { ClearConnections(dropper); }
|
|
IsActive = false;
|
|
}
|
|
|
|
public override void Update(float deltaTime, Camera cam)
|
|
{
|
|
if (nodes.Count == 0) { return; }
|
|
|
|
Character user = item.ParentInventory?.Owner as Character;
|
|
editNodeDelay = (user?.SelectedItem == null) ? editNodeDelay - deltaTime : 0.5f;
|
|
|
|
Submarine sub = item.Submarine;
|
|
if (connections[0] != null && connections[0].Item.Submarine != null) { sub = connections[0].Item.Submarine; }
|
|
if (connections[1] != null && connections[1].Item.Submarine != null) { sub = connections[1].Item.Submarine; }
|
|
|
|
if (Screen.Selected != GameMain.SubEditorScreen)
|
|
{
|
|
if (user != null) { NoAutoLock = true; }
|
|
|
|
//cannot run wires from sub to another
|
|
if (item.Submarine != sub && sub != null && item.Submarine != null)
|
|
{
|
|
ClearConnections();
|
|
return;
|
|
}
|
|
|
|
if (item.CurrentHull == null)
|
|
{
|
|
Structure attachTarget = Structure.GetAttachTarget(item.WorldPosition);
|
|
canPlaceNode = attachTarget != null;
|
|
|
|
sub ??= attachTarget?.Submarine;
|
|
Vector2 attachPos = GetAttachPosition(user);
|
|
newNodePos = sub == null ?
|
|
attachPos :
|
|
attachPos - sub.Position - sub.HiddenSubPosition;
|
|
}
|
|
else
|
|
{
|
|
newNodePos = GetAttachPosition(user);
|
|
if (sub != null) { newNodePos -= sub.HiddenSubPosition; }
|
|
canPlaceNode = true;
|
|
}
|
|
|
|
//prevent the wire from extending too far when rewiring
|
|
if (nodes.Count > 0)
|
|
{
|
|
if (user == null) { return; }
|
|
|
|
Vector2 prevNodePos = nodes[nodes.Count - 1];
|
|
if (sub != null) { prevNodePos += sub.HiddenSubPosition; }
|
|
|
|
currLength = 0.0f;
|
|
for (int i = 0; i < nodes.Count - 1; i++)
|
|
{
|
|
currLength += Vector2.Distance(nodes[i], nodes[i + 1]);
|
|
}
|
|
Vector2 itemPos = item.Position;
|
|
if (sub != null && user.Submarine == null) { prevNodePos += sub.Position; }
|
|
currLength += Vector2.Distance(prevNodePos, itemPos);
|
|
if (currLength > MaxLength)
|
|
{
|
|
Vector2 diff = prevNodePos - user.Position;
|
|
Vector2 pullBackDir = diff == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(diff);
|
|
Vector2 forceDir = pullBackDir;
|
|
if (!user.AnimController.InWater) { forceDir.Y = 0.0f; }
|
|
user.AnimController.Collider.ApplyForce(forceDir * user.Mass * 50.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f);
|
|
if (diff.LengthSquared() > 50.0f * 50.0f)
|
|
{
|
|
user.AnimController.UpdateUseItem(!user.IsClimbing, user.WorldPosition + pullBackDir * Math.Min(150.0f, diff.Length()));
|
|
}
|
|
|
|
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
|
{
|
|
if (currLength > MaxLength * 1.5f)
|
|
{
|
|
ClearConnections();
|
|
#if SERVER
|
|
CreateNetworkEvent();
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if CLIENT
|
|
bool disableGrid = SubEditorScreen.IsSubEditor() && PlayerInput.IsShiftDown();
|
|
newNodePos = disableGrid ? item.Position : RoundNode(item.Position);
|
|
#else
|
|
newNodePos = RoundNode(item.Position);
|
|
#endif
|
|
if (sub != null) { newNodePos -= sub.HiddenSubPosition; }
|
|
canPlaceNode = true;
|
|
}
|
|
|
|
if (item != null)
|
|
{
|
|
Vector2 relativeNodePos = newNodePos - item.Position;
|
|
|
|
if (sub != null)
|
|
{
|
|
relativeNodePos += sub.HiddenSubPosition;
|
|
}
|
|
|
|
sectionExtents = new Vector2(
|
|
Math.Max(Math.Abs(relativeNodePos.X), sectionExtents.X),
|
|
Math.Max(Math.Abs(relativeNodePos.Y), sectionExtents.Y));
|
|
}
|
|
}
|
|
|
|
private Vector2 GetAttachPosition(Character user)
|
|
{
|
|
if (user == null) { return item.Position; }
|
|
|
|
Vector2 mouseDiff = user.CursorWorldPosition - user.WorldPosition;
|
|
mouseDiff = mouseDiff.ClampLength(MaxAttachDistance);
|
|
|
|
return RoundNode(user.Position + mouseDiff);
|
|
}
|
|
|
|
public override bool Use(float deltaTime, Character character = null)
|
|
{
|
|
if (character == null || character != Character.Controlled) { return false; }
|
|
if (character.HasSelectedAnyItem) { return false; }
|
|
#if CLIENT
|
|
if (Screen.Selected == GameMain.SubEditorScreen && !PlayerInput.PrimaryMouseButtonClicked())
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
//clients communicate node addition/removal with network events
|
|
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { return false; }
|
|
|
|
if (newNodePos != Vector2.Zero && canPlaceNode && editNodeDelay <= 0.0f && nodes.Count > 0 &&
|
|
Vector2.DistanceSquared(newNodePos, nodes[nodes.Count - 1]) > MinNodeDistance * MinNodeDistance)
|
|
{
|
|
if (nodes.Count >= MaxNodeCount)
|
|
{
|
|
nodes.RemoveAt(nodes.Count - 1);
|
|
}
|
|
|
|
nodes.Add(newNodePos);
|
|
CleanNodes();
|
|
UpdateSections();
|
|
Drawable = true;
|
|
newNodePos = Vector2.Zero;
|
|
#if CLIENT
|
|
if (GameMain.NetworkMember != null)
|
|
{
|
|
item.CreateClientEvent(this, new ClientEventData(nodes.Count));
|
|
}
|
|
#endif
|
|
}
|
|
editNodeDelay = 0.1f;
|
|
return true;
|
|
}
|
|
|
|
public override bool SecondaryUse(float deltaTime, Character character = null)
|
|
{
|
|
if (character == null || character != Character.Controlled) { return false; }
|
|
|
|
//clients communicate node addition/removal with network events
|
|
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer) { return false; }
|
|
|
|
if (nodes.Count > 1 && editNodeDelay <= 0.0f)
|
|
{
|
|
nodes.RemoveAt(nodes.Count - 1);
|
|
UpdateSections();
|
|
#if CLIENT
|
|
if (GameMain.NetworkMember != null)
|
|
{
|
|
item.CreateClientEvent(this, new ClientEventData(nodes.Count));
|
|
}
|
|
#endif
|
|
}
|
|
editNodeDelay = 0.1f;
|
|
|
|
Drawable = IsActive || sections.Count > 0;
|
|
return true;
|
|
}
|
|
|
|
public override bool Pick(Character picker)
|
|
{
|
|
ClearConnections(picker);
|
|
return true;
|
|
}
|
|
|
|
public List<Vector2> GetNodes()
|
|
{
|
|
return new List<Vector2>(nodes);
|
|
}
|
|
|
|
public void SetNodes(IEnumerable<Vector2> nodes)
|
|
{
|
|
this.nodes = nodes.ToList();
|
|
UpdateSections();
|
|
}
|
|
|
|
public void MoveNode(int index, Vector2 amount)
|
|
{
|
|
if (index < 0 || index >= nodes.Count) return;
|
|
nodes[index] += amount;
|
|
UpdateSections();
|
|
}
|
|
|
|
public void MoveNodes(Vector2 amount)
|
|
{
|
|
for (int i = 0; i < nodes.Count; i++)
|
|
{
|
|
nodes[i] += amount;
|
|
}
|
|
UpdateSections();
|
|
}
|
|
|
|
public void UpdateSections()
|
|
{
|
|
sections.Clear();
|
|
|
|
for (int i = 0; i < nodes.Count - 1; i++)
|
|
{
|
|
sections.Add(new WireSection(nodes[i], nodes[i + 1]));
|
|
}
|
|
Drawable = IsActive || sections.Count > 0;
|
|
Length = sections.Count > 0 ? sections.Sum(s => s.Length) : 0;
|
|
CalculateExtents();
|
|
}
|
|
|
|
private void CalculateExtents()
|
|
{
|
|
sectionExtents = Vector2.Zero;
|
|
if (sections.Count > 0)
|
|
{
|
|
for (int i = 0; i < nodes.Count; i++)
|
|
{
|
|
sectionExtents.X = Math.Max(Math.Abs(nodes[i].X - item.Position.X), sectionExtents.X);
|
|
sectionExtents.Y = Math.Max(Math.Abs(nodes[i].Y - item.Position.Y), sectionExtents.Y);
|
|
}
|
|
}
|
|
#if CLIENT
|
|
item.ResetCachedVisibleSize();
|
|
#endif
|
|
}
|
|
|
|
public void ClearConnections(Character user = null)
|
|
{
|
|
nodes.Clear();
|
|
sections.Clear();
|
|
|
|
foreach (Item item in Item.ItemList)
|
|
{
|
|
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
|
if (connectionPanel != null && connectionPanel.DisconnectedWires.Contains(this) && !item.Removed)
|
|
{
|
|
#if SERVER
|
|
item.CreateServerEvent(connectionPanel);
|
|
#endif
|
|
connectionPanel.DisconnectedWires.Remove(this);
|
|
}
|
|
}
|
|
|
|
#if SERVER
|
|
if (user != null)
|
|
{
|
|
if (connections[0] != null || connections[1] != null)
|
|
{
|
|
GameMain.Server.KarmaManager.OnWireDisconnected(user, this);
|
|
}
|
|
|
|
if (connections[0] != null && connections[1] != null)
|
|
{
|
|
GameServer.Log(GameServer.CharacterLogName(user) + " disconnected a wire from " +
|
|
connections[0].Item.Name + " (" + connections[0].Name + ") to "+
|
|
connections[1].Item.Name + " (" + connections[1].Name + ")", ServerLog.MessageType.ItemInteraction);
|
|
}
|
|
else if (connections[0] != null)
|
|
{
|
|
GameServer.Log(GameServer.CharacterLogName(user) + " disconnected a wire from " +
|
|
connections[0].Item.Name + " (" + connections[0].Name + ")", ServerLog.MessageType.ItemInteraction);
|
|
}
|
|
else if (connections[1] != null)
|
|
{
|
|
GameServer.Log(GameServer.CharacterLogName(user) + " disconnected a wire from " +
|
|
connections[1].Item.Name + " (" + connections[1].Name + ")", ServerLog.MessageType.ItemInteraction);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
SetConnectedDirty();
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
if (connections[i] == null) { continue; }
|
|
|
|
var wire = connections[i].FindWireByItem(item);
|
|
if (wire is null) { continue; }
|
|
#if SERVER
|
|
if (!connections[i].Item.Removed && (!connections[i].Item.Submarine?.Loading ?? true) && (!Level.Loaded?.Generating ?? true))
|
|
{
|
|
connections[i].Item.CreateServerEvent(connections[i].Item.GetComponent<ConnectionPanel>());
|
|
}
|
|
#endif
|
|
connections[i].DisconnectWire(wire);
|
|
connections[i] = null;
|
|
}
|
|
|
|
Drawable = sections.Count > 0;
|
|
}
|
|
|
|
private static Vector2 RoundNode(Vector2 position)
|
|
{
|
|
Vector2 halfGrid = Submarine.GridSize / 2;
|
|
|
|
position += halfGrid;
|
|
position.X = MathUtils.RoundTowardsClosest(position.X, Submarine.GridSize.X / 2.0f);
|
|
position.Y = MathUtils.RoundTowardsClosest(position.Y, Submarine.GridSize.Y / 2.0f);
|
|
return position - halfGrid;
|
|
}
|
|
|
|
public void SetConnectedDirty()
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
if (connections[i]?.Item != null)
|
|
{
|
|
connections[i].Item.GetComponent<PowerTransfer>()?.SetConnectionDirty(connections[i]);
|
|
connections[i].SetRecipientsDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CleanNodes()
|
|
{
|
|
bool removed;
|
|
do
|
|
{
|
|
removed = false;
|
|
for (int i = nodes.Count - 2; i > 0; i--)
|
|
{
|
|
if (Math.Abs(nodes[i - 1].X - nodes[i].X) < 1.0f && Math.Abs(nodes[i + 1].X - nodes[i].X) < 1.0f &&
|
|
Math.Sign(nodes[i - 1].Y - nodes[i].Y) != Math.Sign(nodes[i + 1].Y - nodes[i].Y))
|
|
{
|
|
nodes.RemoveAt(i);
|
|
removed = true;
|
|
}
|
|
else if (Math.Abs(nodes[i - 1].Y - nodes[i].Y) < 1.0f && Math.Abs(nodes[i + 1].Y - nodes[i].Y) < 1.0f &&
|
|
Math.Sign(nodes[i - 1].X - nodes[i].X) != Math.Sign(nodes[i + 1].X - nodes[i].X))
|
|
{
|
|
nodes.RemoveAt(i);
|
|
removed = true;
|
|
}
|
|
}
|
|
|
|
} while (removed);
|
|
}
|
|
|
|
public void FixNodeEnds()
|
|
{
|
|
Item item0 = connections[0]?.Item;
|
|
Item item1 = connections[1]?.Item;
|
|
|
|
if (item0 == null && item1 != null)
|
|
{
|
|
item0 = Item.ItemList.FirstOrDefault(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(this) ?? false);
|
|
}
|
|
else if (item0 != null && item1 == null)
|
|
{
|
|
item1 = Item.ItemList.FirstOrDefault(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(this) ?? false);
|
|
}
|
|
|
|
if (item0 == null || item1 == null || nodes.Count == 0) { return; }
|
|
|
|
Vector2 nodePos = nodes[0];
|
|
|
|
Submarine refSub = item0.Submarine ?? item1.Submarine;
|
|
if (refSub != null) { nodePos += refSub.HiddenSubPosition; }
|
|
|
|
float dist1 = Vector2.DistanceSquared(item0.Position, nodePos);
|
|
float dist2 = Vector2.DistanceSquared(item1.Position, nodePos);
|
|
|
|
//first node is closer to the second item
|
|
//= the nodes are "backwards", need to reverse them
|
|
if (dist1 > dist2)
|
|
{
|
|
nodes.Reverse();
|
|
UpdateSections();
|
|
}
|
|
}
|
|
|
|
private int GetClosestNodeIndex(Vector2 pos, float maxDist, out float closestDist)
|
|
{
|
|
closestDist = 0.0f;
|
|
int closestIndex = -1;
|
|
|
|
for (int i = 0; i < nodes.Count; i++)
|
|
{
|
|
float dist = Vector2.Distance(nodes[i], pos);
|
|
if (dist > maxDist) continue;
|
|
|
|
if (closestIndex == -1 || dist < closestDist)
|
|
{
|
|
closestIndex = i;
|
|
closestDist = dist;
|
|
}
|
|
}
|
|
|
|
return closestIndex;
|
|
}
|
|
|
|
private int GetClosestSectionIndex(Vector2 mousePos, float maxDist, out float closestDist)
|
|
{
|
|
closestDist = 0.0f;
|
|
int closestIndex = -1;
|
|
|
|
maxDist *= maxDist;
|
|
for (int i = 0; i < nodes.Count-1; i++)
|
|
{
|
|
if ((Math.Abs(nodes[i].X - nodes[i + 1].X)<5 || Math.Sign(mousePos.X - nodes[i].X) != Math.Sign(mousePos.X - nodes[i + 1].X)) &&
|
|
(Math.Abs(nodes[i].Y - nodes[i + 1].Y)<5 || Math.Sign(mousePos.Y - nodes[i].Y) != Math.Sign(mousePos.Y - nodes[i + 1].Y)))
|
|
{
|
|
float dist = MathUtils.LineToPointDistanceSquared(nodes[i], nodes[i + 1], mousePos);
|
|
if (dist > maxDist) continue;
|
|
|
|
if (closestIndex == -1 || dist < closestDist)
|
|
{
|
|
closestIndex = i;
|
|
closestDist = dist;
|
|
}
|
|
}
|
|
}
|
|
closestDist = (float)Math.Sqrt(closestDist);
|
|
|
|
return closestIndex;
|
|
}
|
|
|
|
public override void FlipX(bool relativeToSub)
|
|
{
|
|
if (item.ParentInventory != null) { return; }
|
|
#if CLIENT
|
|
if (!relativeToSub)
|
|
{
|
|
if (Screen.Selected != GameMain.SubEditorScreen || (item.Submarine?.Loading ?? false)) { return; }
|
|
}
|
|
#else
|
|
if (!relativeToSub) { return; }
|
|
#endif
|
|
|
|
Vector2 refPos = item.Submarine == null ?
|
|
Vector2.Zero :
|
|
item.Position - item.Submarine.HiddenSubPosition;
|
|
|
|
for (int i = 0; i < nodes.Count; i++)
|
|
{
|
|
nodes[i] = relativeToSub ?
|
|
new Vector2(-nodes[i].X, nodes[i].Y) :
|
|
new Vector2(refPos.X - (nodes[i].X - refPos.X), nodes[i].Y);
|
|
}
|
|
UpdateSections();
|
|
}
|
|
|
|
public override void FlipY(bool relativeToSub)
|
|
{
|
|
Vector2 refPos = item.Submarine == null ?
|
|
Vector2.Zero :
|
|
item.Position - item.Submarine.HiddenSubPosition;
|
|
|
|
for (int i = 0; i < nodes.Count; i++)
|
|
{
|
|
nodes[i] = relativeToSub ?
|
|
new Vector2(nodes[i].X, -nodes[i].Y) :
|
|
new Vector2(nodes[i].X, refPos.Y - (nodes[i].Y - refPos.Y));
|
|
}
|
|
UpdateSections();
|
|
}
|
|
|
|
public static IEnumerable<Vector2> ExtractNodes(XElement element)
|
|
{
|
|
string nodeString = element.GetAttributeString("nodes", "");
|
|
if (nodeString.IsNullOrWhiteSpace()) { yield break; }
|
|
|
|
string[] nodeCoords = nodeString.Split(';');
|
|
for (int i = 0; i < nodeCoords.Length / 2; i++)
|
|
{
|
|
float.TryParse(nodeCoords[i * 2].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float x);
|
|
float.TryParse(nodeCoords[i * 2 + 1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float y);
|
|
yield return new Vector2(x, y);
|
|
}
|
|
}
|
|
|
|
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
|
|
{
|
|
base.Load(componentElement, usePrefabValues, idRemap, isItemSwap);
|
|
|
|
nodes.AddRange(ExtractNodes(componentElement));
|
|
|
|
Drawable = nodes.Any();
|
|
}
|
|
|
|
public override XElement Save(XElement parentElement)
|
|
{
|
|
XElement componentElement = base.Save(parentElement);
|
|
|
|
if (nodes == null || nodes.Count == 0) return componentElement;
|
|
|
|
string[] nodeCoords = new string[nodes.Count * 2];
|
|
for (int i = 0; i < nodes.Count; i++)
|
|
{
|
|
nodeCoords[i * 2] = nodes[i].X.ToString(CultureInfo.InvariantCulture);
|
|
nodeCoords[i * 2 + 1] = nodes[i].Y.ToString(CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
componentElement.Add(new XAttribute("nodes", string.Join(";", nodeCoords)));
|
|
|
|
return componentElement;
|
|
}
|
|
|
|
protected override void ShallowRemoveComponentSpecific()
|
|
{
|
|
/*for (int i = 0; i < 2; i++)
|
|
{
|
|
if (connections[i] == null) continue;
|
|
int wireIndex = connections[i].FindWireIndex(item);
|
|
|
|
if (wireIndex > -1)
|
|
{
|
|
connections[i].AddLink(wireIndex, null);
|
|
}
|
|
}*/
|
|
}
|
|
|
|
protected override void RemoveComponentSpecific()
|
|
{
|
|
if (item.Container?.GetComponent<CircuitBox>() is { } circuitBox)
|
|
{
|
|
circuitBox.RemoveWire(this);
|
|
}
|
|
ClearConnections();
|
|
base.RemoveComponentSpecific();
|
|
|
|
#if CLIENT
|
|
if (DraggingWire == this) { draggingWire = null; }
|
|
overrideSprite?.Remove();
|
|
overrideSprite = null;
|
|
wireSprite = null;
|
|
#endif
|
|
}
|
|
}
|
|
}
|