2 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
5 using System.Globalization;
9 using Microsoft.Xna.Framework.Input;
18 private Vector2 start;
21 private readonly
float angle;
38 angle = MathUtils.VectorToAngle(end - start);
39 Length = Vector2.Distance(start, end);
43 private bool shouldClearConnections =
true;
45 const float MaxAttachDistance = 150.0f;
47 const float MinNodeDistance = 7.0f;
49 const int MaxNodeCount = 255;
50 const int MaxNodesPerNetworkEvent = 30;
52 private List<Vector2> nodes;
53 private readonly List<WireSection> sections;
57 private bool canPlaceNode;
58 private Vector2 newNodePos;
60 private Vector2 sectionExtents;
62 private float currLength;
66 private float editNodeDelay;
74 return locked || connections.Any(c => c !=
null && (c.ConnectionPanel.Locked || c.ConnectionPanel.TemporarilyLocked));
76 set { locked = value; }
81 get {
return connections; }
84 public float Length {
get;
private set; }
93 [
Serialize(
false,
IsPropertySaveable.No, description:
"If enabled, the wire will not be visible in connection panels outside the submarine editor.")]
114 [
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.")]
122 : base(
item, element)
124 nodes =
new List<Vector2>();
125 sections =
new List<WireSection>();
130 InitProjSpecific(element);
137 if (connection == connections[0]) {
return connections[1]; }
138 if (connection == connections[1]) {
return connections[0]; }
145 if (connections[0] !=
null && connections[0].
Item ==
item) {
return true; }
146 return connections[1] !=
null && connections[1].Item ==
item;
151 for (
int i = 0; i < 2; i++)
153 if (connections[i] ==
null || connections[i].
Item !=
item) {
continue; }
155 if (connections[i].Wires.Contains(
this))
159 connections[i].DisconnectWire(
this);
162 connections[i] =
null;
168 if (connection == connections[0]) { connections[0] =
null; }
169 if (connection == connections[1]) { connections[1] =
null; }
181 if (connections[0] ==
null)
183 return Connect(newConnection, 0, addNode, sendNetworkEvent);
185 else if (connections[1] ==
null)
187 return Connect(newConnection, 1, addNode, sendNetworkEvent);
201 public bool Connect(
Connection newConnection,
int connectionIndex,
bool addNode =
true,
bool sendNetworkEvent =
false)
203 for (
int i = 0; i < 2; i++)
205 if (connections[i] == newConnection) {
return false; }
208 if (connectionIndex < 0 || connectionIndex > 1)
210 DebugConsole.ThrowError($
"Error while connecting a wire to {newConnection.Item}: {connectionIndex} is not a valid index.");
213 if (connections[connectionIndex] !=
null)
215 DebugConsole.ThrowError($
"Error while connecting a wire to {newConnection.Item}: a wire is already connected to the index {connectionIndex}.");
219 for (
int i = 0; i < 2; i++)
221 if (connections[i] !=
null && connections[i].
Item == newConnection.
Item)
231 connections[connectionIndex] = newConnection;
236 AddNode(newConnection, connectionIndex);
243 if (connections[0] !=
null && connections[1] !=
null)
247 if (ic ==
this) {
continue; }
263 if (sendNetworkEvent)
268 CreateNetworkEvent();
274 connections[0] ==
null ^ connections[1] ==
null;
283 private void AddNode(
Connection newConnection,
int selectedIndex)
291 connections[selectedIndex] =
null;
297 Vector2 nodePos = RoundNode(newConnection.
Item.
Position);
300 if (nodes.Count > 0 && nodes[0] == nodePos) {
return; }
301 if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) {
return; }
304 int newNodeIndex = 0;
307 if (connections[0] !=
null && connections[0] != newConnection)
309 if (Vector2.DistanceSquared(nodes[0], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
310 Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
312 newNodeIndex = nodes.Count;
315 else if (connections[1] !=
null && connections[1] != newConnection)
317 if (Vector2.DistanceSquared(nodes[0], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
318 Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
320 newNodeIndex = nodes.Count;
323 else if (Vector2.DistanceSquared(nodes[nodes.Count - 1], nodePos) < Vector2.DistanceSquared(nodes[0], nodePos))
325 newNodeIndex = nodes.Count;
329 if (newNodeIndex == 0 && nodes.Count > 1)
331 nodes.Insert(0, nodePos);
359 if (nodes.Count == 0) {
return; }
362 editNodeDelay = (user?.
SelectedItem ==
null) ? editNodeDelay - deltaTime : 0.5f;
365 if (connections[0] !=
null && connections[0].
Item.
Submarine !=
null) { sub = connections[0].Item.
Submarine; }
366 if (connections[1] !=
null && connections[1].
Item.
Submarine !=
null) { sub = connections[1].Item.
Submarine; }
382 canPlaceNode = attachTarget !=
null;
385 Vector2 attachPos = GetAttachPosition(user);
386 newNodePos = sub ==
null ?
392 newNodePos = GetAttachPosition(user);
400 if (user ==
null) {
return; }
402 Vector2 prevNodePos = nodes[nodes.Count - 1];
406 for (
int i = 0; i < nodes.Count - 1; i++)
408 currLength += Vector2.Distance(nodes[i], nodes[i + 1]);
412 currLength += Vector2.Distance(prevNodePos, itemPos);
415 Vector2 diff = prevNodePos - user.
Position;
416 Vector2 pullBackDir = diff == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(diff);
417 Vector2 forceDir = pullBackDir;
420 if (diff.LengthSquared() > 50.0f * 50.0f)
431 CreateNetworkEvent();
460 sectionExtents =
new Vector2(
461 Math.Max(Math.Abs(relativeNodePos.X), sectionExtents.X),
462 Math.Max(Math.Abs(relativeNodePos.Y), sectionExtents.Y));
466 private Vector2 GetAttachPosition(
Character user)
471 mouseDiff = mouseDiff.ClampLength(MaxAttachDistance);
473 return RoundNode(user.
Position + mouseDiff);
479 if (character.HasSelectedAnyItem) {
return false; }
489 if (newNodePos != Vector2.Zero && canPlaceNode && editNodeDelay <= 0.0f && nodes.Count > 0 &&
490 Vector2.DistanceSquared(newNodePos, nodes[nodes.Count - 1]) > MinNodeDistance * MinNodeDistance)
492 if (nodes.Count >= MaxNodeCount)
494 nodes.RemoveAt(nodes.Count - 1);
497 nodes.Add(newNodePos);
501 newNodePos = Vector2.Zero;
505 item.CreateClientEvent(
this,
new ClientEventData(nodes.Count));
509 editNodeDelay = 0.1f;
520 if (nodes.Count > 1 && editNodeDelay <= 0.0f)
522 nodes.RemoveAt(nodes.Count - 1);
527 item.CreateClientEvent(
this,
new ClientEventData(nodes.Count));
531 editNodeDelay = 0.1f;
545 return new List<Vector2>(nodes);
550 this.nodes =
new List<Vector2>(nodes);
556 if (index < 0 || index >= nodes.Count)
return;
557 nodes[index] += amount;
563 for (
int i = 0; i < nodes.Count; i++)
574 for (
int i = 0; i < nodes.Count - 1; i++)
576 sections.Add(
new WireSection(nodes[i], nodes[i + 1]));
579 Length = sections.Count > 0 ? sections.Sum(s => s.Length) : 0;
583 private void CalculateExtents()
585 sectionExtents = Vector2.Zero;
586 if (sections.Count > 0)
588 for (
int i = 0; i < nodes.Count; i++)
590 sectionExtents.X = Math.Max(Math.Abs(nodes[i].X -
item.
Position.X), sectionExtents.X);
591 sectionExtents.Y = Math.Max(Math.Abs(nodes[i].Y -
item.
Position.Y), sectionExtents.Y);
607 if (connectionPanel !=
null && connectionPanel.DisconnectedWires.Contains(
this) && !
item.
Removed)
610 item.CreateServerEvent(connectionPanel);
612 connectionPanel.DisconnectedWires.
Remove(
this);
619 if (connections[0] !=
null || connections[1] !=
null)
621 GameMain.Server.KarmaManager.OnWireDisconnected(user,
this);
624 if (connections[0] !=
null && connections[1] !=
null)
626 GameServer.Log(GameServer.CharacterLogName(user) +
" disconnected a wire from " +
627 connections[0].Item.Name +
" (" + connections[0].Name +
") to "+
628 connections[1].Item.Name +
" (" + connections[1].Name +
")",
ServerLog.
MessageType.ItemInteraction);
630 else if (connections[0] !=
null)
632 GameServer.Log(GameServer.CharacterLogName(user) +
" disconnected a wire from " +
633 connections[0].Item.Name +
" (" + connections[0].Name +
")",
ServerLog.
MessageType.ItemInteraction);
635 else if (connections[1] !=
null)
637 GameServer.Log(GameServer.CharacterLogName(user) +
" disconnected a wire from " +
638 connections[1].Item.Name +
" (" + connections[1].Name +
")",
ServerLog.
MessageType.ItemInteraction);
645 for (
int i = 0; i < 2; i++)
647 if (connections[i] ==
null) {
continue; }
649 var wire = connections[i].FindWireByItem(
item);
650 if (wire is
null) {
continue; }
654 connections[i].Item.CreateServerEvent(connections[i].
Item.GetComponent<
ConnectionPanel>());
657 connections[i].DisconnectWire(wire);
658 connections[i] =
null;
664 private static Vector2 RoundNode(Vector2 position)
668 position += halfGrid;
669 position.X = MathUtils.RoundTowardsClosest(position.X,
Submarine.
GridSize.X / 2.0f);
670 position.Y = MathUtils.RoundTowardsClosest(position.Y,
Submarine.
GridSize.Y / 2.0f);
671 return position - halfGrid;
676 for (
int i = 0; i < 2; i++)
678 if (connections[i]?.
Item !=
null)
680 connections[i].Item.GetComponent<
PowerTransfer>()?.SetConnectionDirty(connections[i]);
681 connections[i].SetRecipientsDirty();
686 private void CleanNodes()
692 for (
int i = nodes.Count - 2; i > 0; i--)
694 if (Math.Abs(nodes[i - 1].X - nodes[i].X) < 1.0f && Math.Abs(nodes[i + 1].X - nodes[i].X) < 1.0f &&
695 Math.Sign(nodes[i - 1].Y - nodes[i].Y) != Math.Sign(nodes[i + 1].Y - nodes[i].Y))
700 else if (Math.Abs(nodes[i - 1].Y - nodes[i].Y) < 1.0f && Math.Abs(nodes[i + 1].Y - nodes[i].Y) < 1.0f &&
701 Math.Sign(nodes[i - 1].X - nodes[i].X) != Math.Sign(nodes[i + 1].X - nodes[i].X))
716 if (item0 ==
null && item1 !=
null)
720 else if (item0 !=
null && item1 ==
null)
725 if (item0 ==
null || item1 ==
null || nodes.Count == 0) {
return; }
727 Vector2 nodePos = nodes[0];
732 float dist1 = Vector2.DistanceSquared(item0.
Position, nodePos);
733 float dist2 = Vector2.DistanceSquared(item1.
Position, nodePos);
744 private int GetClosestNodeIndex(Vector2 pos,
float maxDist, out
float closestDist)
747 int closestIndex = -1;
749 for (
int i = 0; i < nodes.Count; i++)
751 float dist = Vector2.Distance(nodes[i], pos);
752 if (dist > maxDist)
continue;
754 if (closestIndex == -1 || dist < closestDist)
764 private int GetClosestSectionIndex(Vector2 mousePos,
float maxDist, out
float closestDist)
767 int closestIndex = -1;
770 for (
int i = 0; i < nodes.Count-1; i++)
772 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)) &&
773 (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)))
775 float dist = MathUtils.LineToPointDistanceSquared(nodes[i], nodes[i + 1], mousePos);
776 if (dist > maxDist)
continue;
778 if (closestIndex == -1 || dist < closestDist)
785 closestDist = (float)Math.Sqrt(closestDist);
790 public override void FlipX(
bool relativeToSub)
799 if (!relativeToSub) {
return; }
806 for (
int i = 0; i < nodes.Count; i++)
808 nodes[i] = relativeToSub ?
809 new Vector2(-nodes[i].X, nodes[i].Y) :
810 new Vector2(refPos.X - (nodes[i].X - refPos.X), nodes[i].Y);
815 public override void FlipY(
bool relativeToSub)
821 for (
int i = 0; i < nodes.Count; i++)
823 nodes[i] = relativeToSub ?
824 new Vector2(nodes[i].X, -nodes[i].Y) :
825 new Vector2(nodes[i].X, refPos.Y - (nodes[i].Y - refPos.Y));
832 string nodeString = element.GetAttributeString(
"nodes",
"");
833 if (nodeString.IsNullOrWhiteSpace()) { yield
break; }
835 string[] nodeCoords = nodeString.Split(
';');
836 for (
int i = 0; i < nodeCoords.Length / 2; i++)
838 float.TryParse(nodeCoords[i * 2].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out
float x);
839 float.TryParse(nodeCoords[i * 2 + 1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out
float y);
840 yield
return new Vector2(x, y);
846 base.Load(componentElement, usePrefabValues, idRemap, isItemSwap);
853 public override XElement
Save(XElement parentElement)
855 XElement componentElement = base.Save(parentElement);
857 if (nodes ==
null || nodes.Count == 0)
return componentElement;
859 string[] nodeCoords =
new string[nodes.Count * 2];
860 for (
int i = 0; i < nodes.Count; i++)
862 nodeCoords[i * 2] = nodes[i].X.ToString(CultureInfo.InvariantCulture);
863 nodeCoords[i * 2 + 1] = nodes[i].Y.ToString(CultureInfo.InvariantCulture);
866 componentElement.Add(
new XAttribute(
"nodes",
string.Join(
";", nodeCoords)));
868 return componentElement;
888 base.RemoveComponentSpecific();
892 overrideSprite =
null;
void UpdateUseItem(bool allowMovement, Vector2 handWorldPos)
Vector2? CursorWorldPosition
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
static Character? Controlled
override Vector2 Position
readonly AnimController AnimController
virtual Vector2 WorldPosition
static SubEditorScreen SubEditorScreen
static NetworkMember NetworkMember
void ResetCachedVisibleSize()
bool IsShootable
Should the item's Use method be called with the "Use" or with the "Shoot" key?
Inventory ParentInventory
override Vector2? Position
void RemoveContained(Item contained)
static readonly List< Item > ItemList
List< ItemComponent > Components
Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id=Entity.NullEntityID, bool callOnItemLoaded=true)
ConnectionPanel ConnectionPanel
readonly HashSet< Wire > DisconnectedWires
Wires that have been disconnected from the panel, but not removed completely (visible at the bottom o...
The base class for components holding the different functionalities of the item
virtual void Drop(Character dropper, bool setTransform=true)
a Character has dropped the item
WireSection(Vector2 start, Vector2 end)
override void Unequip(Character character)
void SetNodes(List< Vector2 > nodes)
override void Drop(Character dropper, bool setTransform=true)
a Character has dropped the item
bool Connect(Connection newConnection, int connectionIndex, bool addNode=true, bool sendNetworkEvent=false)
Tries to add the given connection to this wire. Note that this only affects the wire - adding the wir...
Wire(Item item, ContentXElement element)
List< Vector2 > GetNodes()
override void ShallowRemoveComponentSpecific()
override XElement Save(XElement parentElement)
override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
override void Equip(Character character)
void MoveNodes(Vector2 amount)
static IEnumerable< Vector2 > ExtractNodes(XElement element)
void RemoveConnection(Item item)
void MoveNode(int index, Vector2 amount)
bool TryConnect(Connection newConnection, bool addNode=true, bool sendNetworkEvent=false)
Tries to add the given connection to this wire. Note that this only affects the wire - adding the wir...
void ClearConnections(Character user=null)
bool IsConnectedTo(Item item)
void RemoveConnection(Connection connection)
override void FlipY(bool relativeToSub)
override void FlipX(bool relativeToSub)
Connection OtherConnection(Connection connection)
override void RemoveComponentSpecific()
override bool SecondaryUse(float deltaTime, Character character=null)
override void Update(float deltaTime, Camera cam)
override bool Pick(Character picker)
a Character has picked the item
override bool Use(float deltaTime, Character character=null)
void ApplyForce(Vector2 force, float maxVelocity=NetConfig.MaxPhysicsBodyVelocity)
static Structure GetAttachTarget(Vector2 worldPosition)
Checks if there's a structure items can be attached to at the given position and returns it.
static bool IsSubEditor()
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
Vector2 HiddenSubPosition
static readonly Vector2 GridSize
override Vector2? Position
Interface for entities that the clients can send events to the server
Interface for entities that the server can send events to the clients