(61d00a474) v0.9.7.1

This commit is contained in:
Regalis
2020-03-04 13:04:10 +01:00
parent 3c50efa5c9
commit 3c09ebe02f
5086 changed files with 786063 additions and 295871 deletions
@@ -0,0 +1,17 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class AdderComponent : ArithmeticComponent
{
public AdderComponent(Item item, XElement element)
: base(item, element)
{
}
protected override float Calculate(float signal1, float signal2)
{
return signal1 + signal2;
}
}
}
@@ -0,0 +1,80 @@
using System;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class AndComponent : ItemComponent
{
protected string output, falseOutput;
//an array to keep track of how long ago a non-zero signal was received on both inputs
protected float[] timeSinceReceived;
//the output is sent if both inputs have received a signal within the timeframe
protected float timeFrame;
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true, description: "The item sends the output if both inputs have received a non-zero signal within the timeframe. If set to 0, the inputs must receive a signal at the same time.")]
public float TimeFrame
{
get { return timeFrame; }
set
{
timeFrame = Math.Max(0.0f, value);
}
}
[InGameEditable, Serialize("1", true, description: "The signal sent when both inputs have received a non-zero signal.")]
public string Output
{
get { return output; }
set { output = value; }
}
[InGameEditable, Serialize("", true, description: "The signal sent when both inputs have not received a non-zero signal (if empty, no signal is sent).")]
public string FalseOutput
{
get { return falseOutput; }
set { falseOutput = value; }
}
public AndComponent(Item item, XElement element)
: base(item, element)
{
timeSinceReceived = new float[] { Math.Max(timeFrame * 2.0f, 0.1f), Math.Max(timeFrame * 2.0f, 0.1f) };
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
bool sendOutput = true;
for (int i = 0; i < timeSinceReceived.Length; i++)
{
if (timeSinceReceived[i] > timeFrame) sendOutput = false;
timeSinceReceived[i] += deltaTime;
}
string signalOut = sendOutput ? output : falseOutput;
if (string.IsNullOrEmpty(signalOut)) return;
item.SendSignal(0, signalOut, "signal_out", null);
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "signal_in1":
if (signal == "0") return;
timeSinceReceived[0] = 0.0f;
break;
case "signal_in2":
if (signal == "0") return;
timeSinceReceived[1] = 0.0f;
break;
case "set_output":
output = signal;
break;
}
}
}
}
@@ -0,0 +1,87 @@
using Microsoft.Xna.Framework;
using System;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
abstract class ArithmeticComponent : ItemComponent
{
//an array to keep track of how long ago a signal was received on both inputs
protected float[] timeSinceReceived;
protected float[] receivedSignal;
//the output is sent if both inputs have received a signal within the timeframe
protected float timeFrame;
[Serialize(999999.0f, true, description: "The output of the item is restricted below this value."),
InGameEditable(MinValueFloat = -999999.0f, MaxValueFloat = 999999.0f)]
public float ClampMax
{
get;
set;
}
[Serialize(-999999.0f, true, description: "The output of the item is restricted above this value."),
InGameEditable(MinValueFloat = -999999.0f, MaxValueFloat = 999999.0f)]
public float ClampMin
{
get;
set;
}
[InGameEditable(DecimalCount = 2),
Serialize(0.0f, true, description: "The item must have received signals to both inputs within this timeframe to output the sum of the signals." +
" If set to 0, the inputs must be received at the same time.")]
public float TimeFrame
{
get { return timeFrame; }
set
{
timeFrame = Math.Max(0.0f, value);
}
}
public ArithmeticComponent(Item item, XElement element)
: base(item, element)
{
timeSinceReceived = new float[] { Math.Max(timeFrame * 2.0f, 0.1f), Math.Max(timeFrame * 2.0f, 0.1f) };
receivedSignal = new float[2];
}
sealed public override void Update(float deltaTime, Camera cam)
{
for (int i = 0; i < timeSinceReceived.Length; i++)
{
if (timeSinceReceived[i] > timeFrame)
{
IsActive = false;
return;
}
timeSinceReceived[i] += deltaTime;
}
float output = Calculate(receivedSignal[0], receivedSignal[1]);
item.SendSignal(0, MathHelper.Clamp(output, ClampMin, ClampMax).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
}
protected abstract float Calculate(float signal1, float signal2);
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "signal_in1":
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[0]);
timeSinceReceived[0] = 0.0f;
IsActive = true;
break;
case "signal_in2":
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[1]);
timeSinceReceived[1] = 0.0f;
IsActive = true;
break;
}
}
}
}
@@ -0,0 +1,56 @@
using System;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class ColorComponent : ItemComponent
{
protected float[] receivedSignal;
private string output = "0,0,0,0";
public ColorComponent(Item item, XElement element)
: base(item, element)
{
receivedSignal = new float[4];
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
item.SendSignal(0, output, "signal_out", null);
}
private void UpdateOutput()
{
output = receivedSignal[0].ToString("G", CultureInfo.InvariantCulture);
output += "," + receivedSignal[1].ToString("G", CultureInfo.InvariantCulture);
output += "," + receivedSignal[2].ToString("G", CultureInfo.InvariantCulture);
output += "," + receivedSignal[3].ToString("G", CultureInfo.InvariantCulture);
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "signal_r":
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[0]);
UpdateOutput();
break;
case "signal_g":
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[1]);
UpdateOutput();
break;
case "signal_b":
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[2]);
UpdateOutput();
break;
case "signal_a":
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[3]);
UpdateOutput();
break;
}
}
}
}
@@ -0,0 +1,333 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Connection
{
//how many wires can be linked to a single connector
public const int MaxLinked = 5;
public readonly string Name;
public readonly string DisplayName;
private Wire[] wires;
public IEnumerable<Wire> Wires
{
get { return wires; }
}
private Item item;
public readonly bool IsOutput;
public readonly List<StatusEffect> Effects;
public readonly ushort[] wireId;
public bool IsPower
{
get;
private set;
}
private bool recipientsDirty = true;
private List<Connection> recipients = new List<Connection>();
public List<Connection> Recipients
{
get
{
if (recipientsDirty) RefreshRecipients();
return recipients;
}
}
public Item Item
{
get { return item; }
}
public ConnectionPanel ConnectionPanel
{
get;
private set;
}
public override string ToString()
{
return "Connection (" + item.Name + ", " + Name + ")";
}
public Connection(XElement element, ConnectionPanel connectionPanel)
{
#if CLIENT
if (connector == null)
{
connector = GUI.Style.GetComponentStyle("ConnectionPanelConnector").Sprites[GUIComponent.ComponentState.None][0].Sprite;
wireVertical = GUI.Style.GetComponentStyle("ConnectionPanelWire").Sprites[GUIComponent.ComponentState.None][0].Sprite;
connectionSprite = GUI.Style.GetComponentStyle("ConnectionPanelConnection").Sprites[GUIComponent.ComponentState.None][0].Sprite;
connectionSpriteHighlight = GUI.Style.GetComponentStyle("ConnectionPanelConnection").Sprites[GUIComponent.ComponentState.Hover][0].Sprite;
screwSprites = GUI.Style.GetComponentStyle("ConnectionPanelScrew").Sprites[GUIComponent.ComponentState.None].Select(s => s.Sprite).ToList();
}
#endif
ConnectionPanel = connectionPanel;
item = connectionPanel.Item;
wires = new Wire[MaxLinked];
IsOutput = element.Name.ToString() == "output";
Name = element.GetAttributeString("name", IsOutput ? "output" : "input");
string displayNameTag = "", fallbackTag = "";
//if displayname is not present, attempt to find it from the prefab
if (element.Attribute("displayname") == null)
{
foreach (XElement subElement in item.Prefab.ConfigElement.Elements())
{
if (!subElement.Name.ToString().Equals("connectionpanel", StringComparison.OrdinalIgnoreCase)) { continue; }
foreach (XElement connectionElement in subElement.Elements())
{
string prefabConnectionName = element.GetAttributeString("name", null);
if (prefabConnectionName == Name)
{
displayNameTag = connectionElement.GetAttributeString("displayname", "");
fallbackTag = connectionElement.GetAttributeString("fallbackdisplayname", "");
}
}
}
}
else
{
displayNameTag = element.GetAttributeString("displayname", "");
fallbackTag = element.GetAttributeString("fallbackdisplayname", null);
}
if (!string.IsNullOrEmpty(displayNameTag))
{
//extract the tag parts in case the tags contains variables
string tagWithoutVariables = displayNameTag?.Split('~')?.FirstOrDefault();
string fallbackTagWithoutVariables = fallbackTag?.Split('~')?.FirstOrDefault();
//use displayNameTag if found, otherwise fallBack
if (TextManager.ContainsTag(tagWithoutVariables))
{
DisplayName = TextManager.GetServerMessage(displayNameTag);
}
else if (TextManager.ContainsTag(fallbackTagWithoutVariables))
{
DisplayName = TextManager.GetServerMessage(fallbackTag);
}
}
if (string.IsNullOrEmpty(DisplayName))
{
#if DEBUG
DebugConsole.ThrowError("Missing display name in connection " + item.Name + ": " + Name);
#endif
DisplayName = Name;
}
IsPower = Name == "power_in" || Name == "power" || Name == "power_out";
Effects = new List<StatusEffect>();
wireId = new ushort[MaxLinked];
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "link":
int index = -1;
for (int i = 0; i < MaxLinked; i++)
{
if (wireId[i] < 1) index = i;
}
if (index == -1) break;
int id = subElement.GetAttributeInt("w", 0);
if (id < 0) id = 0;
wireId[index] = (ushort)id;
break;
case "statuseffect":
Effects.Add(StatusEffect.Load(subElement, item.Name + ", connection " + Name));
break;
}
}
}
private void RefreshRecipients()
{
recipients.Clear();
for (int i = 0; i < MaxLinked; i++)
{
if (wires[i] == null) continue;
Connection recipient = wires[i].OtherConnection(this);
if (recipient != null) recipients.Add(recipient);
}
recipientsDirty = false;
}
public int FindEmptyIndex()
{
for (int i = 0; i < MaxLinked; i++)
{
if (wires[i] == null) return i;
}
return -1;
}
public int FindWireIndex(Wire wire)
{
for (int i = 0; i < MaxLinked; i++)
{
if (wires[i] == wire) return i;
}
return -1;
}
public int FindWireIndex(Item wireItem)
{
for (int i = 0; i < MaxLinked; i++)
{
if (wires[i] == null && wireItem == null) return i;
if (wires[i] != null && wires[i].Item == wireItem) return i;
}
return -1;
}
public void TryAddLink(Wire wire)
{
for (int i = 0; i < MaxLinked; i++)
{
if (wires[i] == null)
{
SetWire(i, wire);
return;
}
}
}
public void SetWire(int index, Wire wire)
{
Wire previousWire = wires[index];
if (wire != previousWire && previousWire != null)
{
var otherConnection = previousWire.OtherConnection(this);
if (otherConnection != null)
{
otherConnection.recipientsDirty = true;
}
}
wires[index] = wire;
recipientsDirty = true;
if (wire != null)
{
ConnectionPanel.DisconnectedWires.Remove(wire);
var otherConnection = wire.OtherConnection(this);
if (otherConnection != null)
{
otherConnection.recipientsDirty = true;
}
}
}
public void SendSignal(int stepsTaken, string signal, Item source, Character sender, float power, float signalStrength = 1.0f)
{
for (int i = 0; i < MaxLinked; i++)
{
if (wires[i] == null) { continue; }
Connection recipient = wires[i].OtherConnection(this);
if (recipient == null) { continue; }
if (recipient.item == this.item || recipient.item == source) { continue; }
source?.LastSentSignalRecipients.Add(recipient.item);
foreach (ItemComponent ic in recipient.item.Components)
{
ic.ReceiveSignal(stepsTaken, signal, recipient, source, sender, power, signalStrength);
}
foreach (StatusEffect effect in recipient.Effects)
{
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step);
}
}
}
public void SendPowerProbeSignal(Item source, float power)
{
for (int i = 0; i < MaxLinked; i++)
{
if (wires[i] == null) { continue; }
Connection recipient = wires[i].OtherConnection(this);
if (recipient == null) { continue; }
recipient.item.GetComponent<Powered>()?.ReceivePowerProbeSignal(recipient, source, power);
}
}
public void ClearConnections()
{
for (int i = 0; i < MaxLinked; i++)
{
if (wires[i] == null) continue;
wires[i].RemoveConnection(this);
wires[i] = null;
recipientsDirty = true;
}
}
public void ConnectLinked()
{
if (wireId == null) return;
for (int i = 0; i < MaxLinked; i++)
{
if (wireId[i] == 0) { continue; }
if (!(Entity.FindEntityByID(wireId[i]) is Item wireItem)) { continue; }
wires[i] = wireItem.GetComponent<Wire>();
recipientsDirty = true;
if (wires[i] != null)
{
if (wires[i].Item.body != null) wires[i].Item.body.Enabled = false;
wires[i].Connect(this, false, false);
}
}
}
public void Save(XElement parentElement)
{
XElement newElement = new XElement(IsOutput ? "output" : "input", new XAttribute("name", Name));
Array.Sort(wires, delegate (Wire wire1, Wire wire2)
{
if (wire1 == null) return 1;
if (wire2 == null) return -1;
return wire1.Item.ID.CompareTo(wire2.Item.ID);
});
for (int i = 0; i < MaxLinked; i++)
{
if (wires[i] == null) continue;
newElement.Add(new XElement("link",
new XAttribute("w", wires[i].Item.ID.ToString())));
}
parentElement.Add(newElement);
}
}
}
@@ -0,0 +1,314 @@
using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class ConnectionPanel : ItemComponent, IServerSerializable, IClientSerializable
{
public List<Connection> Connections;
private Character user;
/// <summary>
/// Wires that have been disconnected from the panel, but not removed completely (visible at the bottom of the connection panel).
/// </summary>
public readonly HashSet<Wire> DisconnectedWires = new HashSet<Wire>();
private List<ushort> disconnectedWireIds;
[Editable, Serialize(false, true, description: "Locked connection panels cannot be rewired in-game.")]
public bool Locked
{
get;
set;
}
//connection panels can't be deactivated externally (by signals or status effects)
public override bool IsActive
{
get { return base.IsActive; }
set { /*do nothing*/ }
}
public Character User
{
get { return user; }
}
public ConnectionPanel(Item item, XElement element)
: base(item, element)
{
Connections = new List<Connection>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString())
{
case "input":
Connections.Add(new Connection(subElement, this));
break;
case "output":
Connections.Add(new Connection(subElement, this));
break;
}
}
base.IsActive = true;
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public override void OnMapLoaded()
{
foreach (Connection c in Connections)
{
c.ConnectLinked();
}
if (disconnectedWireIds != null)
{
foreach (ushort disconnectedWireId in disconnectedWireIds)
{
if (!(Entity.FindEntityByID(disconnectedWireId) is Item wireItem)) { continue; }
Wire wire = wireItem.GetComponent<Wire>();
if (wire != null)
{
DisconnectedWires.Add(wire);
base.IsActive = true;
}
}
}
}
public override void OnItemLoaded()
{
if (item.body != null)
{
var holdable = item.GetComponent<Holdable>();
if (holdable == null || !holdable.Attachable)
{
DebugConsole.ThrowError("Item \"" + item.Name + "\" has a ConnectionPanel component," +
" but cannot be wired because it has an active physics body that cannot be attached to a wall." +
" Remove the physics body or add a Holdable component with the Attachable attribute set to true.");
}
}
}
public void MoveConnectedWires(Vector2 amount)
{
Vector2 wireNodeOffset = item.Submarine == null ? Vector2.Zero : item.Submarine.HiddenSubPosition + amount;
foreach (Connection c in Connections)
{
foreach (Wire wire in c.Wires)
{
if (wire == null) continue;
#if CLIENT
if (wire.Item.IsSelected) continue;
#endif
var wireNodes = wire.GetNodes();
if (wireNodes.Count == 0) continue;
if (Submarine.RectContains(item.Rect, wireNodes[0] + wireNodeOffset))
{
wire.MoveNode(0, amount);
}
else if (Submarine.RectContains(item.Rect, wireNodes[wireNodes.Count - 1] + wireNodeOffset))
{
wire.MoveNode(wireNodes.Count - 1, amount);
}
}
}
}
public override void Update(float deltaTime, Camera cam)
{
UpdateProjSpecific(deltaTime);
if (user == null || user.SelectedConstruction != item)
{
#if SERVER
if (user != null) { item.CreateServerEvent(this); }
#endif
user = null;
if (DisconnectedWires.Count == 0) { base.IsActive = false; }
return;
}
if (!user.Enabled || !HasRequiredItems(user, addMessage: false))
{
user = null;
base.IsActive = false;
return;
}
user.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * (((float)Timing.TotalTime / 10.0f) % 0.1f));
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
Update(deltaTime, cam);
}
partial void UpdateProjSpecific(float deltaTime);
public override bool Select(Character picker)
{
//attaching wires to items with a body is not allowed
//(signal items remove their bodies when attached to a wall)
if (item.body != null)
{
return false;
}
user = picker;
#if SERVER
if (user != null) { item.CreateServerEvent(this); }
#endif
base.IsActive = true;
return true;
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || character != user) { return false; }
return true;
}
/// <summary>
/// Check if the character manages to succesfully rewire the panel, and if not, apply OnFailure effects
/// </summary>
public bool CheckCharacterSuccess(Character character)
{
if (character == null) { return false; }
var powered = item.GetComponent<Powered>();
if (powered != null)
{
//unpowered panels can be rewired without a risk of electrical shock
if (powered.Voltage < 0.1f) { return true; }
}
float degreeOfSuccess = DegreeOfSuccess(character);
if (Rand.Range(0.0f, 0.5f) < degreeOfSuccess) { return true; }
item.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
return false;
}
public override void Load(XElement element, bool usePrefabValues)
{
base.Load(element, usePrefabValues);
List<Connection> loadedConnections = new List<Connection>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString())
{
case "input":
loadedConnections.Add(new Connection(subElement, this));
break;
case "output":
loadedConnections.Add(new Connection(subElement, this));
break;
}
}
for (int i = 0; i < loadedConnections.Count && i < Connections.Count; i++)
{
loadedConnections[i].wireId.CopyTo(Connections[i].wireId, 0);
}
disconnectedWireIds = element.GetAttributeUshortArray("disconnectedwires", new ushort[0]).ToList();
}
public override XElement Save(XElement parentElement)
{
XElement componentElement = base.Save(parentElement);
foreach (Connection c in Connections)
{
c.Save(componentElement);
}
if (DisconnectedWires.Count > 0)
{
componentElement.Add(new XAttribute("disconnectedwires", string.Join(",", DisconnectedWires.Select(w => w.Item.ID))));
}
return componentElement;
}
protected override void ShallowRemoveComponentSpecific()
{
//do nothing
}
protected override void RemoveComponentSpecific()
{
foreach (Wire wire in DisconnectedWires.ToList())
{
if (wire.OtherConnection(null) == null) //wire not connected to anything else
{
wire.Item.Drop(null);
}
}
DisconnectedWires.Clear();
foreach (Connection c in Connections)
{
foreach (Wire wire in c.Wires)
{
if (wire == null) { continue; }
if (wire.OtherConnection(c) == null) //wire not connected to anything else
{
wire.Item.Drop(null);
}
else
{
wire.RemoveConnection(item);
}
}
}
#if CLIENT
rewireSoundChannel?.FadeOutAndDispose();
rewireSoundChannel = null;
#endif
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
{
//do nothing
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{
#if CLIENT
TriggerRewiringSound();
#endif
foreach (Connection connection in Connections)
{
foreach (Wire wire in connection.Wires)
{
msg.Write(wire?.Item == null ? (ushort)0 : wire.Item.ID);
}
}
msg.Write((ushort)DisconnectedWires.Count());
foreach (Wire disconnectedWire in DisconnectedWires)
{
msg.Write(disconnectedWire.Item.ID);
}
}
}
}
@@ -0,0 +1,199 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class CustomInterface : ItemComponent, IClientSerializable, IServerSerializable
{
class CustomInterfaceElement : ISerializableEntity
{
public bool ContinuousSignal;
public bool State;
public string ConnectionName;
public Connection Connection;
[Serialize("", false, translationTextTag: "Label.", description: "The text displayed on this button/tickbox."), Editable]
public string Label { get; set; }
[Serialize("1", false, description: "The signal sent out when this button is pressed or this tickbox checked."), Editable]
public string Signal { get; set; }
public string Name => "CustomInterfaceElement";
public Dictionary<string, SerializableProperty> SerializableProperties { get; set; }
public List<StatusEffect> StatusEffects = new List<StatusEffect>();
public CustomInterfaceElement(XElement element)
{
Label = element.GetAttributeString("text", "");
ConnectionName = element.GetAttributeString("connection", "");
Signal = element.GetAttributeString("signal", "1");
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().Equals("statuseffect", System.StringComparison.OrdinalIgnoreCase))
{
StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName: "custom interface element (label " + Label + ")"));
}
}
}
}
private string[] labels;
[Serialize("", true, description: "The texts displayed on the buttons/tickboxes, separated by commas.")]
public string Labels
{
get { return string.Join(",", labels); }
set
{
if (value == null) { return; }
string[] splitValues = value == "" ? new string[0] : value.Split(',');
if (customInterfaceElementList.Count > 0)
{
UpdateLabels(splitValues);
}
}
}
private string[] signals;
[Serialize("", true, description: "The signals sent when the buttons are pressed or the tickboxes checked, separated by commas.")]
public string Signals
{
//use semicolon as a separator because comma may be needed in the signals (for color or vector values for example)
//kind of hacky, we should probably add support for (string) arrays to SerializableEntityEditor so this wouldn't be needed
get { return signals == null ? "" : string.Join(";", signals); }
set
{
if (value == null) { return; }
string[] splitValues = value == "" ? new string[0] : value.Split(';');
if (customInterfaceElementList.Count > 0)
{
signals = new string[customInterfaceElementList.Count];
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
signals[i] = i < splitValues.Length ? splitValues[i] : customInterfaceElementList[i].Signal;
customInterfaceElementList[i].Signal = signals[i];
}
}
}
}
private List<CustomInterfaceElement> customInterfaceElementList = new List<CustomInterfaceElement>();
public CustomInterface(Item item, XElement element)
: base(item, element)
{
int i = 0;
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "button":
var button = new CustomInterfaceElement(subElement)
{
ContinuousSignal = false
};
if (string.IsNullOrEmpty(button.Label))
{
button.Label = "Signal out " + customInterfaceElementList.Count(e => !e.ContinuousSignal);
}
customInterfaceElementList.Add(button);
break;
case "tickbox":
var tickBox = new CustomInterfaceElement(subElement)
{
ContinuousSignal = true
};
if (string.IsNullOrEmpty(tickBox.Label))
{
tickBox.Label = "Signal out " + customInterfaceElementList.Count(e => !e.ContinuousSignal);
}
customInterfaceElementList.Add(tickBox);
break;
}
i++;
}
IsActive = true;
InitProjSpecific(element);
Labels = element.GetAttributeString("labels", "");
Signals = element.GetAttributeString("signals", "");
}
private void UpdateLabels(string[] newLabels)
{
labels = new string[customInterfaceElementList.Count];
for (int i = 0; i < labels.Length; i++)
{
labels[i] = i < newLabels.Length ? newLabels[i] : customInterfaceElementList[i].Label;
if (Screen.Selected != GameMain.SubEditorScreen)
{
customInterfaceElementList[i].Label = TextManager.Get(labels[i], returnNull: true) ?? labels[i];
}
else
{
customInterfaceElementList[i].Label = labels[i];
}
}
UpdateLabelsProjSpecific();
}
public override void OnItemLoaded()
{
foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
{
ciElement.Connection = item.Connections?.FirstOrDefault(c => c.Name == ciElement.ConnectionName);
}
}
partial void UpdateLabelsProjSpecific();
partial void InitProjSpecific(XElement element);
private void ButtonClicked(CustomInterfaceElement btnElement)
{
if (btnElement == null) return;
if (btnElement.Connection != null)
{
item.SendSignal(0, btnElement.Signal, btnElement.Connection, sender: null, source: item);
}
foreach (StatusEffect effect in btnElement.StatusEffects)
{
item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f);
}
}
private void TickBoxToggled(CustomInterfaceElement tickBoxElement, bool state)
{
if (tickBoxElement == null) { return; }
tickBoxElement.State = state;
}
public override void Update(float deltaTime, Camera cam)
{
UpdateProjSpecific();
foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
{
if (!ciElement.ContinuousSignal) { continue; }
//TODO: allow changing output when a tickbox is not selected
if (!string.IsNullOrEmpty(ciElement.Signal) && ciElement.Connection != null)
{
item.SendSignal(0, ciElement.State ? ciElement.Signal : "0", ciElement.Connection, sender: null, source: item);
}
foreach (StatusEffect effect in ciElement.StatusEffects)
{
item.ApplyStatusEffect(effect, ciElement.State ? ActionType.OnUse : ActionType.OnSecondaryUse, 1.0f, null, null, null, true, false);
}
}
}
partial void UpdateProjSpecific();
public override XElement Save(XElement parentElement)
{
labels = customInterfaceElementList.Select(ci => ci.Label).ToArray();
signals = customInterfaceElementList.Select(ci => ci.Signal).ToArray();
return base.Save(parentElement);
}
}
}
@@ -0,0 +1,114 @@
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class DelayComponent : ItemComponent
{
class DelayedSignal
{
public readonly string Signal;
public readonly float SignalStrength;
//in number of frames
public int SendTimer;
//in number of frames
public int SendDuration;
public DelayedSignal(string signal, float signalStrength, int sendTimer)
{
Signal = signal;
SignalStrength = signalStrength;
SendTimer = sendTimer;
}
}
private int signalQueueSize;
private int delayTicks;
private Queue<DelayedSignal> signalQueue;
private DelayedSignal prevQueuedSignal;
private float delay;
[InGameEditable(MinValueFloat = 0.0f, MaxValueFloat = 60.0f, DecimalCount = 2), Serialize(1.0f, true, description: "How long the item delays the signals (in seconds).")]
public float Delay
{
get { return delay; }
set
{
if (value == delay) { return; }
delay = value;
delayTicks = (int)(delay / Timing.Step);
signalQueueSize = delayTicks * 2;
}
}
[InGameEditable, Serialize(false, true, description: "Should the component discard previously received signals when a new one is received.")]
public bool ResetWhenSignalReceived
{
get;
set;
}
[InGameEditable, Serialize(false, true, description: "Should the component discard previously received signals when the incoming signal changes.")]
public bool ResetWhenDifferentSignalReceived
{
get;
set;
}
public DelayComponent(Item item, XElement element)
: base (item, element)
{
signalQueue = new Queue<DelayedSignal>();
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
foreach (var val in signalQueue)
{
val.SendTimer -= 1;
}
while (signalQueue.Count > 0 && signalQueue.Peek().SendTimer <= 0)
{
var signalOut = signalQueue.Peek();
signalOut.SendDuration -= 1;
item.SendSignal(0, signalOut.Signal, "signal_out", null, signalStrength: signalOut.SignalStrength);
if (signalOut.SendDuration <= 0) { signalQueue.Dequeue(); } else { break; }
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "signal_in":
if (signalQueue.Count >= signalQueueSize) { return; }
if (ResetWhenSignalReceived) { prevQueuedSignal = null; signalQueue.Clear(); }
if (ResetWhenDifferentSignalReceived && signalQueue.Count > 0 && signalQueue.Peek().Signal != signal)
{
prevQueuedSignal = null;
signalQueue.Clear();
}
if (prevQueuedSignal != null &&
prevQueuedSignal.Signal == signal &&
MathUtils.NearlyEqual(prevQueuedSignal.SignalStrength, signalStrength) &&
((prevQueuedSignal.SendTimer + prevQueuedSignal.SendDuration == delayTicks) || (prevQueuedSignal.SendTimer <= 0 && prevQueuedSignal.SendDuration > 0)))
{
prevQueuedSignal.SendDuration += 1;
return;
}
prevQueuedSignal = new DelayedSignal(signal, signalStrength, delayTicks)
{
SendDuration = 1
};
signalQueue.Enqueue(prevQueuedSignal);
break;
}
}
}
}
@@ -0,0 +1,19 @@
using System;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class DivideComponent : ArithmeticComponent
{
public DivideComponent(Item item, XElement element)
: base(item, element)
{
}
protected override float Calculate(float signal1, float signal2)
{
return signal1 / signal2;
}
}
}
@@ -0,0 +1,83 @@
using System;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class EqualsComponent : ItemComponent
{
protected string output, falseOutput;
//an array to keep track of how long ago a signal was received on both inputs
protected float[] timeSinceReceived;
protected string[] receivedSignal;
//the output is sent if both inputs have received a signal within the timeframe
protected float timeFrame;
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signals are equal.")]
public string Output
{
get { return output; }
set { output = value; }
}
[InGameEditable, Serialize("", true, description: "The signal this item outputs when the received signals are not equal.")]
public string FalseOutput
{
get { return falseOutput; }
set { falseOutput = value; }
}
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true, description: "The maximum amount of time between the received signals. If set to 0, the signals must be received at the same time.")]
public float TimeFrame
{
get { return timeFrame; }
set
{
timeFrame = Math.Max(0.0f, value);
}
}
public EqualsComponent(Item item, XElement element)
: base(item, element)
{
timeSinceReceived = new float[] { Math.Max(timeFrame * 2.0f, 0.1f), Math.Max(timeFrame * 2.0f, 0.1f) };
receivedSignal = new string[2];
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
bool sendOutput = false;
for (int i = 0; i < timeSinceReceived.Length; i++)
{
if (timeSinceReceived[i] <= timeFrame) sendOutput = true;
timeSinceReceived[i] += deltaTime;
}
if (sendOutput)
{
string signalOut = receivedSignal[0] == receivedSignal[1] ? output : falseOutput;
if (string.IsNullOrEmpty(signalOut)) return;
item.SendSignal(0, signalOut, "signal_out", null);
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "signal_in1":
receivedSignal[0] = signal;
timeSinceReceived[0] = 0.0f;
break;
case "signal_in2":
receivedSignal[1] = signal;
timeSinceReceived[1] = 0.0f;
break;
}
}
}
}
@@ -0,0 +1,43 @@
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class ExponentiationComponent : ItemComponent
{
private float exponent;
[InGameEditable, Serialize(1.0f, false, description: "The exponent of the operation.")]
public float Exponent
{
get
{
return exponent;
}
set
{
exponent = value;
}
}
public ExponentiationComponent(Item item, XElement element)
: base(item, element)
{
IsActive = true;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
{
switch (connection.Name)
{
case "set_exponent":
case "exponent":
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out exponent);
break;
case "signal_in":
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float value);
item.SendSignal(0, MathUtils.Pow(value, Exponent).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
}
}
}
}
@@ -0,0 +1,67 @@
using System;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class FunctionComponent : ItemComponent
{
public enum FunctionType
{
Round,
Ceil,
Floor,
Factorial,
AbsoluteValue,
SquareRoot
}
[Serialize(FunctionType.Round, false, description: "Which kind of function to run the input through.")]
public FunctionType Function
{
get; set;
}
public FunctionComponent(Item item, XElement element)
: base(item, element)
{
IsActive = true;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
{
if (connection.Name != "signal_in") return;
if (!float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float value)) return;
switch (Function)
{
case FunctionType.Round:
item.SendSignal(0, Math.Round(value).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
case FunctionType.Ceil:
item.SendSignal(0, Math.Ceiling(value).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
case FunctionType.Floor:
item.SendSignal(0, Math.Floor(value).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
case FunctionType.Factorial:
int intVal = (int)Math.Min(value, 20);
ulong factorial = 1;
for (int i = intVal; i > 0; i--)
{
factorial *= (ulong)i;
}
item.SendSignal(0, factorial.ToString(), "signal_out", null);
break;
case FunctionType.AbsoluteValue:
item.SendSignal(0, Math.Abs(value).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
case FunctionType.SquareRoot:
double square = value > 0 ? Math.Sqrt(value) : 0;
item.SendSignal(0, square.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
default:
throw new NotImplementedException($"Function {Function} has not been implemented.");
}
}
}
}
@@ -0,0 +1,41 @@
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class GreaterComponent : EqualsComponent
{
private float val1, val2;
public GreaterComponent(Item item, XElement element)
: base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
bool sendOutput = false;
for (int i = 0; i < timeSinceReceived.Length; i++)
{
if (timeSinceReceived[i] <= timeFrame) sendOutput = true;
timeSinceReceived[i] += deltaTime;
}
if (sendOutput)
{
string signalOut = val1 > val2 ? output : falseOutput;
if (string.IsNullOrEmpty(signalOut)) return;
item.SendSignal(0, signalOut, "signal_out", null);
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
float.TryParse(receivedSignal[0], NumberStyles.Float, CultureInfo.InvariantCulture, out val1);
float.TryParse(receivedSignal[1], NumberStyles.Float, CultureInfo.InvariantCulture, out val2);
}
}
}
@@ -0,0 +1,285 @@
using Microsoft.Xna.Framework;
using System;
using System.Xml.Linq;
using Barotrauma.Networking;
#if CLIENT
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Lights;
#endif
namespace Barotrauma.Items.Components
{
partial class LightComponent : Powered, IServerSerializable, IDrawableComponent
{
private Color lightColor;
private float lightBrightness;
private float blinkFrequency;
private float range;
private float flicker;
private bool castShadows;
private bool drawBehindSubs;
private float blinkTimer;
private bool itemLoaded;
public PhysicsBody ParentBody;
[Serialize(100.0f, true, description: "The range of the emitted light. Higher values are more performance-intensive."),
Editable(MinValueFloat = 0.0f, MaxValueFloat = 2048.0f)]
public float Range
{
get { return range; }
set
{
range = MathHelper.Clamp(value, 0.0f, 4096.0f);
#if CLIENT
if (light != null) { light.Range = range; }
#endif
}
}
public float Rotation;
[Editable, Serialize(true, true, description: "Should structures cast shadows when light from this light source hits them. " +
"Disabling shadows increases the performance of the game, and is recommended for lights with a short range.")]
public bool CastShadows
{
get { return castShadows; }
set
{
castShadows = value;
#if CLIENT
if (light != null) light.CastShadows = value;
#endif
}
}
[Editable, Serialize(false, true, description: "Lights drawn behind submarines don't cast any shadows and are much faster to draw than shadow-casting lights. " +
"It's recommended to enable this on decorative lights outside the submarine's hull.")]
public bool DrawBehindSubs
{
get { return drawBehindSubs; }
set
{
drawBehindSubs = value;
#if CLIENT
if (light != null) light.IsBackground = drawBehindSubs;
#endif
}
}
[Editable, Serialize(false, true, description: "Is the light currently on.")]
public bool IsOn
{
get { return IsActive; }
set
{
if (IsActive == value) { return; }
IsActive = value;
#if SERVER
if (GameMain.Server != null && itemLoaded) { item.CreateServerEvent(this); }
#endif
}
}
[Serialize(0.0f, false, description: "How heavily the light flickers. 0 = no flickering, 1 = the light will alternate between completely dark and full brightness.")]
public float Flicker
{
get { return flicker; }
set
{
flicker = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
[Editable, Serialize(0.0f, true, description: "How rapidly the light blinks on and off (in Hz). 0 = no blinking.")]
public float BlinkFrequency
{
get { return blinkFrequency; }
set
{
blinkFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
}
}
[InGameEditable, Serialize("255,255,255,255", true, description: "The color of the emitted light (R,G,B,A).")]
public Color LightColor
{
get { return lightColor; }
set
{
lightColor = value;
#if CLIENT
if (light != null) light.Color = IsActive ? lightColor : Color.Transparent;
#endif
}
}
public override void Move(Vector2 amount)
{
#if CLIENT
light.Position += amount;
#endif
}
public override bool IsActive
{
get
{
return base.IsActive;
}
set
{
if (base.IsActive == value) { return; }
base.IsActive = value;
SetLightSourceState(value, value ? lightBrightness : 0.0f);
}
}
public LightComponent(Item item, XElement element)
: base(item, element)
{
#if CLIENT
light = new LightSource(element)
{
ParentSub = item.CurrentHull?.Submarine,
Position = item.Position,
CastShadows = castShadows,
IsBackground = drawBehindSubs,
SpriteScale = Vector2.One * item.Scale,
Range = range
};
#endif
IsActive = IsOn;
item.AddTag("light");
}
public override void OnItemLoaded()
{
base.OnItemLoaded();
itemLoaded = true;
SetLightSourceState(IsActive, lightBrightness);
}
public override void Update(float deltaTime, Camera cam)
{
if (item.AiTarget != null)
{
UpdateAITarget(item.AiTarget);
}
UpdateOnActiveEffects(deltaTime);
#if CLIENT
light.ParentSub = item.Submarine;
#endif
if (item.Container != null)
{
SetLightSourceState(false, 0.0f);
return;
}
#if CLIENT
light.Position = ParentBody != null ? ParentBody.Position : item.Position;
#endif
PhysicsBody body = ParentBody ?? item.body;
if (body != null)
{
#if CLIENT
light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi;
light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;
#endif
if (!body.Enabled)
{
SetLightSourceState(false, 0.0f);
return;
}
}
else
{
#if CLIENT
light.Rotation = -Rotation;
#endif
}
currPowerConsumption = powerConsumption;
if (Rand.Range(0.0f, 1.0f) < 0.05f && Voltage < Rand.Range(0.0f, MinVoltage))
{
#if CLIENT
if (Voltage > 0.1f)
{
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
}
#endif
lightBrightness = 0.0f;
}
else
{
lightBrightness = MathHelper.Lerp(lightBrightness, Math.Min(Voltage, 1.0f), 0.1f);
}
if (blinkFrequency > 0.0f)
{
blinkTimer = (blinkTimer + deltaTime * blinkFrequency) % 1.0f;
}
if (blinkTimer > 0.5f)
{
SetLightSourceState(false, lightBrightness);
}
else
{
SetLightSourceState(true, lightBrightness * (1.0f - Rand.Range(0.0f, flicker)));
}
if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; }
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
SetLightSourceState(false, 0.0f);
}
public override bool Use(float deltaTime, Character character = null)
{
return true;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "toggle":
IsActive = !IsActive;
break;
case "set_state":
IsActive = (signal != "0");
break;
case "set_color":
LightColor = XMLExtensions.ParseColor(signal, false);
break;
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(IsOn);
}
private void UpdateAITarget(AITarget target)
{
if (!IsActive) { return; }
if (target.MaxSightRange <= 0)
{
target.MaxSightRange = Range * 5;
}
target.SightRange = Math.Max(target.SightRange, target.MaxSightRange * lightBrightness);
}
partial void SetLightSourceState(bool enabled, float brightness);
}
}
@@ -0,0 +1,40 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class MemoryComponent : ItemComponent
{
[InGameEditable, Serialize("", true, description: "The currently stored signal the item outputs.")]
public string Value
{
get;
set;
}
protected bool writeable = true;
public MemoryComponent(Item item, XElement element)
: base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
item.SendSignal(0, Value, "signal_out", null);
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "signal_in":
if (writeable) { Value = signal; }
break;
case "signal_store":
writeable = (signal == "1");
break;
}
}
}
}
@@ -0,0 +1,41 @@
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class ModuloComponent : ItemComponent
{
private float modulus;
[InGameEditable, Serialize(1.0f, false, description: "The modulus of the operation. Must be non-zero.")]
public float Modulus
{
get { return modulus; }
set
{
modulus = MathUtils.NearlyEqual(value, 0.0f) ? 1.0f : value;
}
}
public ModuloComponent(Item item, XElement element) : base(item, element)
{
IsActive = true;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
{
switch (connection.Name)
{
case "set_modulus":
case "modulus":
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newModulus);
Modulus = newModulus;
break;
case "signal_in":
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float value);
item.SendSignal(0, (value % modulus).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
}
}
}
}
@@ -0,0 +1,161 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class MotionSensor : ItemComponent
{
private const float UpdateInterval = 0.1f;
private float rangeX, rangeY;
private Vector2 detectOffset;
private float updateTimer;
[Serialize(false, false, description: "Has the item currently detected movement. Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
public bool MotionDetected { get; set; }
[Editable, Serialize(false, true, description: "Should the sensor only detect the movement of humans?")]
public bool OnlyHumans
{
get;
set;
}
[Editable, Serialize(false, true, description: "Should the sensor ignore the bodies of dead characters?")]
public bool IgnoreDead
{
get;
set;
}
[InGameEditable, Serialize(0.0f, true, description: "Horizontal detection range.")]
public float RangeX
{
get { return rangeX; }
set
{
rangeX = MathHelper.Clamp(value, 0.0f, 1000.0f);
}
}
[InGameEditable, Serialize(0.0f, true, description: "Vertical movement detection range.")]
public float RangeY
{
get { return rangeY; }
set
{
rangeY = MathHelper.Clamp(value, 0.0f, 1000.0f);
}
}
[Editable, Serialize("0,0", true, description: "The position to detect the movement at relative to the item. For example, 0,100 would detect movement 100 units above the item.")]
public Vector2 DetectOffset
{
get { return detectOffset; }
set
{
detectOffset = value;
detectOffset.X = MathHelper.Clamp(value.X, -rangeX, rangeX);
detectOffset.Y = MathHelper.Clamp(value.Y, -rangeY, rangeY);
}
}
[InGameEditable, Serialize("1", true, description: "The signal the item outputs when it has detected movement.")]
public string Output { get; set; }
[InGameEditable, Serialize("", true, description: "The signal the item outputs when it has not detected movement.")]
public string FalseOutput { get; set; }
[Editable(DecimalCount = 3), Serialize(0.01f, true, description: "How fast the objects within the detector's range have to be moving (in m/s).")]
public float MinimumVelocity
{
get;
set;
}
public MotionSensor(Item item, XElement element)
: base(item, element)
{
IsActive = true;
//backwards compatibility
if (element.Attribute("range") != null)
{
rangeX = rangeY = element.GetAttributeFloat("range", 0.0f);
}
}
public override void Update(float deltaTime, Camera cam)
{
string signalOut = MotionDetected ? Output : FalseOutput;
if (!string.IsNullOrEmpty(signalOut)) item.SendSignal(1, signalOut, "state_out", null);
updateTimer -= deltaTime;
if (updateTimer > 0.0f) return;
MotionDetected = false;
updateTimer = UpdateInterval;
if (item.body != null && item.body.Enabled)
{
if (Math.Abs(item.body.LinearVelocity.X) > MinimumVelocity || Math.Abs(item.body.LinearVelocity.Y) > MinimumVelocity)
{
MotionDetected = true;
}
}
Vector2 detectPos = item.WorldPosition + detectOffset;
Rectangle detectRect = new Rectangle((int)(detectPos.X - rangeX), (int)(detectPos.Y - rangeY), (int)(rangeX * 2), (int)(rangeY * 2));
float broadRangeX = Math.Max(rangeX * 2, 500);
float broadRangeY = Math.Max(rangeY * 2, 500);
foreach (Character c in Character.CharacterList)
{
if (IgnoreDead && c.IsDead) { continue; }
if (OnlyHumans && !c.IsHuman) { continue; }
//do a rough check based on the position of the character's collider first
//before the more accurate limb-based check
if (Math.Abs(c.WorldPosition.X - detectPos.X) > broadRangeX || Math.Abs(c.WorldPosition.Y - detectPos.Y) > broadRangeY)
{
continue;
}
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb.LinearVelocity.LengthSquared() <= MinimumVelocity * MinimumVelocity) continue;
if (MathUtils.CircleIntersectsRectangle(limb.WorldPosition, ConvertUnits.ToDisplayUnits(limb.body.GetMaxExtent()), detectRect))
{
MotionDetected = true;
break;
}
}
}
}
public override void FlipX(bool relativeToSub)
{
detectOffset.X = -detectOffset.X;
}
public override void FlipY(bool relativeToSub)
{
detectOffset.Y = -detectOffset.Y;
}
public override XElement Save(XElement parentElement)
{
Vector2 prevDetectOffset = detectOffset;
//undo flipping before saving
if (item.FlippedX) { detectOffset.X = -detectOffset.X; }
if (item.FlippedY) { detectOffset.Y = -detectOffset.Y; }
XElement element = base.Save(parentElement);
detectOffset = prevDetectOffset;
return element;
}
}
}
@@ -0,0 +1,17 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class MultiplyComponent : ArithmeticComponent
{
public MultiplyComponent(Item item, XElement element)
: base(item, element)
{
}
protected override float Calculate(float signal1, float signal2)
{
return signal1 * signal2;
}
}
}
@@ -0,0 +1,19 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class NotComponent : ItemComponent
{
public NotComponent(Item item, XElement element)
: base (item, element)
{
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (connection.Name != "signal_in") return;
item.SendSignal(stepsTaken, signal == "0" ? "1" : "0", "signal_out", sender, 0.0f, source, signalStrength);
}
}
}
@@ -0,0 +1,28 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class OrComponent : AndComponent
{
public OrComponent(Item item, XElement element)
: base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
bool sendOutput = false;
for (int i = 0; i < timeSinceReceived.Length; i++)
{
if (timeSinceReceived[i] <= timeFrame) sendOutput = true;
timeSinceReceived[i] += deltaTime;
}
string signalOut = sendOutput ? output : falseOutput;
if (string.IsNullOrEmpty(signalOut)) return;
item.SendSignal(0, signalOut, "signal_out", null);
}
}
}
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
namespace Barotrauma.Items.Components
{
class OscillatorComponent : ItemComponent
{
public enum WaveType
{
Pulse,
Sine,
Square,
}
private float frequency;
private float phase;
[InGameEditable, Serialize(WaveType.Pulse, true, description: "What kind of a signal the item outputs." +
" Pulse: periodically sends out a signal of 1." +
" Sine: sends out a sine wave oscillating between -1 and 1." +
" Square: sends out a signal that alternates between 0 and 1.")]
public WaveType OutputType
{
get;
set;
}
[InGameEditable(DecimalCount = 2), Serialize(1.0f, true, description: "How fast the signal oscillates, or how fast the pulses are sent (in Hz).")]
public float Frequency
{
get { return frequency; }
set
{
//capped to 240 Hz (= 4 signals per frame) to prevent players
//from wrecking the performance by setting the value too high
frequency = MathHelper.Clamp(value, 0.0f, 240.0f);
}
}
public OscillatorComponent(Item item, XElement element) :
base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
switch (OutputType)
{
case WaveType.Pulse:
if (frequency <= 0.0f) return;
phase += deltaTime;
float pulseInterval = 1.0f / frequency;
while (phase >= pulseInterval)
{
item.SendSignal(0, "1", "signal_out", null);
phase -= pulseInterval;
}
break;
case WaveType.Square:
phase = (phase + deltaTime * frequency) % 1.0f;
item.SendSignal(0, phase < 0.5f ? "0" : "1", "signal_out", null);
break;
case WaveType.Sine:
phase = (phase + deltaTime * frequency) % 1.0f;
item.SendSignal(0, Math.Sin(phase * MathHelper.TwoPi).ToString(CultureInfo.InvariantCulture), "signal_out", null);
break;
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "set_frequency":
case "frequency_in":
float newFrequency;
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out newFrequency))
{
Frequency = newFrequency;
}
IsActive = true;
break;
case "set_outputtype":
case "set_wavetype":
WaveType newOutputType;
if (Enum.TryParse(signal, out newOutputType))
{
OutputType = newOutputType;
}
break;
}
}
}
}
@@ -0,0 +1,21 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class OxygenDetector : ItemComponent
{
public OxygenDetector(Item item, XElement element)
: base (item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
if (item.CurrentHull == null) return;
item.SendSignal(0, ((int)item.CurrentHull.OxygenPercentage).ToString(), "signal_out", null);
}
}
}
@@ -0,0 +1,104 @@
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class RegExFindComponent : ItemComponent
{
private string expression;
private string receivedSignal;
private string previousReceivedSignal;
private bool previousResult;
private Regex regex;
private bool nonContinuousOutputSent;
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signal matches the regular expression.")]
public string Output { get; set; }
[Serialize("0", true, description: "The signal this item outputs when the received signal does not match the regular expression.")]
public string FalseOutput { get; set; }
[InGameEditable, Serialize(true, true, description: "Should the component keep sending the output even after it stops receiving a signal, or only send an output when it receives a signal.")]
public bool ContinuousOutput { get; set; }
[InGameEditable, Serialize("", true, description: "The regular expression used to check the incoming signals.")]
public string Expression
{
get { return expression; }
set
{
if (expression == value) return;
expression = value;
previousReceivedSignal = "";
try
{
regex = new Regex(@expression);
}
catch
{
item.SendSignal(0, "ERROR", "signal_out", null);
return;
}
}
}
public RegExFindComponent(Item item, XElement element)
: base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
if (string.IsNullOrWhiteSpace(expression) || regex == null) return;
if (receivedSignal != previousReceivedSignal && receivedSignal != null)
{
try
{
Match match = regex.Match(receivedSignal);
previousResult = match.Success;
previousReceivedSignal = receivedSignal;
}
catch
{
item.SendSignal(0, "ERROR", "signal_out", null);
previousResult = false;
return;
}
}
string signalOut = previousResult ? Output : FalseOutput;
if (ContinuousOutput)
{
if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(0, signalOut, "signal_out", null); }
}
else if (!nonContinuousOutputSent)
{
if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(0, signalOut, "signal_out", null); }
nonContinuousOutputSent = true;
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "signal_in":
receivedSignal = signal;
nonContinuousOutputSent = false;
break;
case "set_output":
Output = signal;
break;
}
}
}
}
@@ -0,0 +1,217 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class RelayComponent : PowerTransfer, IServerSerializable
{
private float maxPower;
private bool isOn;
private float throttlePowerOutput;
private static readonly Dictionary<string, string> connectionPairs = new Dictionary<string, string>
{
{ "power_in", "power_out"},
{ "signal_in", "signal_out" },
{ "signal_in1", "signal_out1" },
{ "signal_in2", "signal_out2" },
{ "signal_in3", "signal_out3" },
{ "signal_in4", "signal_out4" },
{ "signal_in5", "signal_out5" }
};
public float DisplayLoad { get; set; }
[Editable, Serialize(1000.0f, true, description: "The maximum amount of power that can pass through the item.")]
public float MaxPower
{
get { return maxPower; }
set
{
maxPower = Math.Max(0.0f, value);
}
}
[Editable, Serialize(false, true, description: "Can the relay currently pass power and signals through it.")]
public bool IsOn
{
get
{
return isOn;
}
set
{
isOn = value;
CanTransfer = value;
if (!isOn)
{
currPowerConsumption = 0.0f;
}
}
}
public RelayComponent(Item item, XElement element)
: base(item, element)
{
IsActive = true;
throttlePowerOutput = MaxPower;
}
public override void OnItemLoaded()
{
base.OnItemLoaded();
var connections = Item.Connections;
if (connections != null)
{
foreach (KeyValuePair<string, string> connectionPair in connectionPairs)
{
if (connections.Any(c => c.Name == connectionPair.Key) && !connections.Any(c => c.Name == connectionPair.Value))
{
DebugConsole.ThrowError("Error in item \"" + Name + "\" - matching connection pair not found for the connection \"" + connectionPair.Key + "\" (expecting \"" + connectionPair.Value + "\").");
}
else if (connections.Any(c => c.Name == connectionPair.Value) && !connections.Any(c => c.Name == connectionPair.Key))
{
DebugConsole.ThrowError("Error in item \"" + Name + "\" - matching connection pair not found for the connection \"" + connectionPair.Value + "\" (expecting \"" + connectionPair.Key + "\").");
}
}
}
}
public override void Update(float deltaTime, Camera cam)
{
RefreshConnections();
item.SendSignal(0, IsOn ? "1" : "0", "state_out", null);
if (!CanTransfer) { Voltage = 0.0f; return; }
if (isBroken)
{
SetAllConnectionsDirty();
isBroken = false;
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (powerOut != null)
{
bool overloaded = false;
foreach (Connection recipient in powerOut.Recipients)
{
var pt = recipient.Item.GetComponent<PowerTransfer>();
if (pt != null)
{
float overload = -pt.CurrPowerConsumption - pt.PowerLoad;
throttlePowerOutput += overload * deltaTime * 0.5f;
overloaded = overload > 1.0f;
}
}
throttlePowerOutput = overloaded ?
MathHelper.Clamp(throttlePowerOutput, 0.0f, MaxPower):
Math.Max(throttlePowerOutput - MaxPower * 0.1f * deltaTime, 0.0f);
}
if (Math.Min(-currPowerConsumption, PowerLoad) > maxPower && CanBeOverloaded)
{
item.Condition = 0.0f;
}
}
public override void ReceivePowerProbeSignal(Connection connection, Item source, float power)
{
if (!IsOn || item.Condition <= 0.0f) { return; }
//we've already received this signal
if (lastPowerProbeRecipients.Contains(this)) { return; }
lastPowerProbeRecipients.Add(this);
if (power < 0.0f)
{
if (!connection.IsOutput || powerIn == null) { return; }
//power being drawn from the power_out connection
DisplayLoad -= Math.Min(power, 0.0f);
powerLoad -= Math.Min(power + throttlePowerOutput, 0.0f);
//pass the load to items connected to the input
powerIn.SendPowerProbeSignal(source, Math.Max(power, -MaxPower));
}
else
{
if (connection.IsOutput || powerOut == null) { return; }
//power being supplied to the power_in connection
if (currPowerConsumption - power < -MaxPower)
{
power += MaxPower + (currPowerConsumption - power);
}
currPowerConsumption -= power;
foreach (Connection recipient in powerOut.Recipients)
{
if (!recipient.IsPower) { continue; }
var powered = recipient.Item.GetComponent<Powered>();
if (powered == null) { continue; }
float load = powered.CurrPowerConsumption;
var powerTransfer = powered as PowerTransfer;
if (powerTransfer != null) { load = powerTransfer.PowerLoad; }
float powerOut = power * (load / Math.Max(powerLoad + throttlePowerOutput, 0.01f));
powered.ReceivePowerProbeSignal(recipient, source, Math.Min(powerOut, power));
}
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (item.Condition <= 0.0f || connection.IsPower) { return; }
if (connectionPairs.TryGetValue(connection.Name, out string outConnection))
{
if (!IsOn) { return; }
item.SendSignal(stepsTaken, signal, outConnection, sender, power, source, signalStrength);
}
else if (connection.Name == "toggle")
{
SetState(!IsOn, false);
}
else if (connection.Name == "set_state")
{
SetState(signal != "0", false);
}
}
public void SetState(bool on, bool isNetworkMessage)
{
#if CLIENT
if (GameMain.Client != null && !isNetworkMessage) return;
#endif
#if SERVER
if (on != IsOn && GameMain.Server != null)
{
item.CreateServerEvent(this);
}
#endif
IsOn = on;
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(isOn);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
SetState(msg.ReadBoolean(), true);
}
}
}
@@ -0,0 +1,40 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class SignalCheckComponent : ItemComponent
{
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signal matches the target signal.")]
public string Output { get; set; }
[InGameEditable, Serialize("0", true, description: "The signal this item outputs when the received signal does not match the target signal.")]
public string FalseOutput { get; set; }
[InGameEditable, Serialize("", true, description: "The value to compare the received signals against.")]
public string TargetSignal { get; set; }
public SignalCheckComponent(Item item, XElement element)
: base(item, element)
{
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "signal_in":
string signalOut = (signal == TargetSignal) ? Output : FalseOutput;
if (string.IsNullOrWhiteSpace(signalOut)) return;
item.SendSignal(stepsTaken, signalOut, "signal_out", sender, signalStrength);
break;
case "set_output":
Output = signal;
break;
case "set_targetsignal":
TargetSignal = signal;
break;
}
}
}
}
@@ -0,0 +1,25 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class SmokeDetector : ItemComponent
{
[Serialize(50.0f, false, description: "How large the fire has to be for the detector to react to it.")]
public float FireSizeThreshold
{
get; set;
}
public SmokeDetector(Item item, XElement element)
: base (item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
item.SendSignal(0, item.CurrentHull != null && item.CurrentHull.FireSources.Any(fs => fs.Size.X > FireSizeThreshold) ? "1" : "0", "signal_out", null);
}
}
}
@@ -0,0 +1,17 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class SubtractComponent : ArithmeticComponent
{
public SubtractComponent(Item item, XElement element)
: base(item, element)
{
}
protected override float Calculate(float signal1, float signal2)
{
return signal1 - signal2;
}
}
}
@@ -0,0 +1,51 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Terminal : ItemComponent
{
private const int MaxMessageLength = 150;
public string DisplayedWelcomeMessage
{
get;
private set;
}
private string welcomeMessage;
[InGameEditable, Serialize("", true, "Message to be displayed on the terminal display when it is first opened.", translationTextTag = "terminalwelcomemsg.")]
public string WelcomeMessage
{
get { return welcomeMessage; }
set
{
if (welcomeMessage == value) { return; }
welcomeMessage = value;
DisplayedWelcomeMessage = TextManager.Get(welcomeMessage, returnNull: true) ?? welcomeMessage;
}
}
private string OutputValue { get; set; }
public Terminal(Item item, XElement element)
: base(item, element)
{
IsActive = true;
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
partial void ShowOnDisplay(string input);
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
{
if (connection.Name != "signal_in") { return; }
if (signal.Length > MaxMessageLength)
{
signal = signal.Substring(0, MaxMessageLength);
}
ShowOnDisplay(signal);
}
}
}
@@ -0,0 +1,108 @@
using Microsoft.Xna.Framework;
using System;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class TrigonometricFunctionComponent : ItemComponent
{
public enum FunctionType
{
Sin,
Cos,
Tan,
Asin,
Acos,
Atan,
}
protected float[] receivedSignal = new float[2];
[Serialize(FunctionType.Sin, false, description: "Which kind of function to run the input through.")]
public FunctionType Function
{
get; set;
}
[InGameEditable, Serialize(false, true, description: "If set to true, the trigonometric function uses radians instead of degrees.")]
public bool UseRadians
{
get; set;
}
public TrigonometricFunctionComponent(Item item, XElement element)
: base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
//reset received signals
receivedSignal[0] = float.NaN;
receivedSignal[1] = float.NaN;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
{
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float value);
switch (Function)
{
case FunctionType.Sin:
if (!UseRadians) { value = MathHelper.ToRadians(value); }
item.SendSignal(0, ((float)Math.Sin(value)).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
case FunctionType.Cos:
if (!UseRadians) { value = MathHelper.ToRadians(value); }
item.SendSignal(0, ((float)Math.Cos(value)).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
case FunctionType.Tan:
if (!UseRadians) { value = MathHelper.ToRadians(value); }
item.SendSignal(0, ((float)Math.Tan(value)).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
case FunctionType.Asin:
{
float angle = (float)Math.Asin(value);
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
item.SendSignal(0, angle.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
}
break;
case FunctionType.Acos:
{
float angle = (float)Math.Acos(value);
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
item.SendSignal(0, angle.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
}
break;
case FunctionType.Atan:
if (connection.Name == "signal_in_x")
{
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[0]);
}
else if (connection.Name == "signal_in_y")
{
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[1]);
if (!float.IsNaN(receivedSignal[0]) && !float.IsNaN(receivedSignal[1]))
{
float angle = (float)Math.Atan2(receivedSignal[1], receivedSignal[0]);
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
item.SendSignal(0, angle.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
}
}
else
{
float angle = (float)Math.Atan(value);
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
item.SendSignal(0, angle.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
}
break;
default:
throw new NotImplementedException($"Function {Function} has not been implemented.");
}
}
}
}
@@ -0,0 +1,64 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class WaterDetector : ItemComponent
{
//how often the detector can switch from state to another
const float StateSwitchInterval = 1.0f;
private bool isInWater;
private float stateSwitchDelay;
[InGameEditable, Serialize("1", true, description: "The signal the item sends out when it's underwater.")]
public string Output { get; set; }
[InGameEditable, Serialize("0", true, description: "The signal the item sends out when it's not underwater.")]
public string FalseOutput { get; set; }
public WaterDetector(Item item, XElement element)
: base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
if (stateSwitchDelay > 0.0f)
{
stateSwitchDelay -= deltaTime;
}
else
{
bool prevState = isInWater;
isInWater = false;
if (item.InWater)
{
//item in water -> we definitely want to send the True output
isInWater = true;
}
else if (item.CurrentHull != null)
{
//item in not water -> check if there's water anywhere within the rect of the item
if (item.CurrentHull.Surface > item.CurrentHull.Rect.Y - item.CurrentHull.Rect.Height + 1 &&
item.CurrentHull.Surface > item.Rect.Y - item.Rect.Height)
{
isInWater = true;
}
}
if (prevState != isInWater)
{
stateSwitchDelay = StateSwitchInterval;
}
}
string signalOut = isInWater ? Output : FalseOutput;
if (!string.IsNullOrEmpty(signalOut))
{
item.SendSignal(0, signalOut, "signal_out", null);
}
}
}
}
@@ -0,0 +1,191 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class WifiComponent : ItemComponent
{
private static List<WifiComponent> list = new List<WifiComponent>();
private float range;
private int channel;
private float chatMsgCooldown;
private string prevSignal;
[Serialize(Character.TeamType.None, true, description: "WiFi components can only communicate with components that have the same Team ID.")]
public Character.TeamType TeamID { get; set; }
[Editable, Serialize(20000.0f, false, description: "How close the recipient has to be to receive a signal from this WiFi component.")]
public float Range
{
get { return range; }
set { range = Math.Max(value, 0.0f); }
}
[InGameEditable, Serialize(1, true, description: "WiFi components can only communicate with components that use the same channel.")]
public int Channel
{
get { return channel; }
set
{
channel = MathHelper.Clamp(value, 0, 10000);
}
}
[Serialize(false, false, description: "Can the component communicate with wifi components in another team's submarine (e.g. enemy sub in Combat missions, respawn shuttle). Needs to be enabled on both the component transmitting the signal and the component receiving it.")]
public bool AllowCrossTeamCommunication
{
get;
set;
}
[Editable, Serialize(false, false, description: "If enabled, any signals received from another chat-linked wifi component are displayed " +
"as chat messages in the chatbox of the player holding the item.")]
public bool LinkToChat
{
get;
set;
}
[Editable, Serialize(1.0f, true, description: "How many seconds have to pass between signals for a message to be displayed in the chatbox. " +
"Setting this to a very low value is not recommended, because it may cause an excessive amount of chat messages to be created " +
"if there are chat-linked wifi components that transmit a continuous signal.")]
public float MinChatMessageInterval
{
get;
set;
}
[Editable, Serialize(false, true, description: "If set to true, the component will only create chat messages when the received signal changes.")]
public bool DiscardDuplicateChatMessages
{
get;
set;
}
public WifiComponent(Item item, XElement element)
: base (item, element)
{
list.Add(this);
IsActive = true;
}
public bool CanTransmit()
{
return HasRequiredContainedItems(user: null, addMessage: false);
}
public IEnumerable<WifiComponent> GetReceiversInRange()
{
return list.Where(w => w != this && w.CanReceive(this));
}
public bool CanReceive(WifiComponent sender)
{
if (sender == null || sender.channel != channel) { return false; }
if (sender.TeamID != TeamID && !AllowCrossTeamCommunication)
{
return false;
}
if (Vector2.DistanceSquared(item.WorldPosition, sender.item.WorldPosition) > sender.range * sender.range) { return false; }
return HasRequiredContainedItems(user: null, addMessage: false);
}
public override void Update(float deltaTime, Camera cam)
{
chatMsgCooldown -= deltaTime;
}
public void TransmitSignal(int stepsTaken, string signal, Item source, Character sender, bool sendToChat, float signalStrength = 1.0f)
{
var senderComponent = source?.GetComponent<WifiComponent>();
if (senderComponent != null && !CanReceive(senderComponent)) return;
bool chatMsgSent = false;
var receivers = GetReceiversInRange();
foreach (WifiComponent wifiComp in receivers)
{
//signal strength diminishes by distance
float sentSignalStrength = signalStrength *
MathHelper.Clamp(1.0f - (Vector2.Distance(item.WorldPosition, wifiComp.item.WorldPosition) / wifiComp.range), 0.0f, 1.0f);
wifiComp.item.SendSignal(stepsTaken, signal, "signal_out", sender, 0, source, sentSignalStrength);
if (source != null)
{
foreach (Item receiverItem in wifiComp.item.LastSentSignalRecipients)
{
if (!source.LastSentSignalRecipients.Contains(receiverItem))
{
source.LastSentSignalRecipients.Add(receiverItem);
}
}
}
if (DiscardDuplicateChatMessages && signal == prevSignal) continue;
if (LinkToChat && wifiComp.LinkToChat && chatMsgCooldown <= 0.0f && sendToChat)
{
if (wifiComp.item.ParentInventory != null &&
wifiComp.item.ParentInventory.Owner != null &&
GameMain.NetworkMember != null)
{
string chatMsg = signal;
if (senderComponent != null)
{
chatMsg = ChatMessage.ApplyDistanceEffect(chatMsg, 1.0f - sentSignalStrength);
}
if (chatMsg.Length > ChatMessage.MaxLength) chatMsg = chatMsg.Substring(0, ChatMessage.MaxLength);
if (string.IsNullOrEmpty(chatMsg)) continue;
#if CLIENT
if (wifiComp.item.ParentInventory.Owner == Character.Controlled)
{
if (GameMain.Client == null)
GameMain.NetworkMember.AddChatMessage(signal, ChatMessageType.Radio, source == null ? "" : source.Name);
}
#endif
#if SERVER
if (GameMain.Server != null)
{
Client recipientClient = GameMain.Server.ConnectedClients.Find(c => c.Character == wifiComp.item.ParentInventory.Owner);
if (recipientClient != null)
{
GameMain.Server.SendDirectChatMessage(
ChatMessage.Create(source == null ? "" : source.Name, chatMsg, ChatMessageType.Radio, null), recipientClient);
}
}
#endif
chatMsgSent = true;
}
}
}
if (chatMsgSent) chatMsgCooldown = MinChatMessageInterval;
prevSignal = signal;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (connection == null || connection.Name != "signal_in") return;
TransmitSignal(stepsTaken, signal, source, sender, true, signalStrength);
}
protected override void RemoveComponentSpecific()
{
list.Remove(this);
}
}
}
@@ -0,0 +1,766 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Wire : ItemComponent, IDrawableComponent, IServerSerializable, IClientSerializable
{
partial class WireSection
{
private Vector2 start;
private Vector2 end;
private readonly float angle;
private 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);
}
}
const float MaxAttachDistance = 150.0f;
const float MinNodeDistance = 15.0f;
const int MaxNodeCount = 255;
const int MaxNodesPerNetworkEvent = 30;
private List<Vector2> nodes;
private readonly List<WireSection> sections;
private Connection[] connections;
private bool canPlaceNode;
private Vector2 newNodePos;
private Vector2 sectionExtents;
public bool Hidden;
private float removeNodeDelay;
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);
}
set { locked = value; }
}
public Connection[] Connections
{
get { return connections; }
}
[Serialize(5000.0f, false, description: "The maximum distance the wire can extend (in pixels).")]
public float MaxLength
{
get;
set;
}
public Wire(Item item, XElement 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(XElement 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;
foreach (Wire wire in connections[i].Wires)
{
if (wire != this) continue;
SetConnectedDirty();
connections[i].SetWire(connections[i].FindWireIndex(wire), null);
}
connections[i] = null;
}
}
public void RemoveConnection(Connection connection)
{
if (connection == connections[0]) { connections[0] = null; }
if (connection == connections[1]) { connections[1] = null; }
SetConnectedDirty();
}
public bool Connect(Connection newConnection, bool addNode = true, bool sendNetworkEvent = false)
{
for (int i = 0; i < 2; i++)
{
if (connections[i] == newConnection) { return false; }
}
if (!connections.Any(c => c == null)) { 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);
for (int i = 0; i < 2; i++)
{
if (connections[i] != null) { continue; }
connections[i] = newConnection;
FixNodeEnds();
if (!addNode) { break; }
Submarine refSub = newConnection.Item.Submarine;
if (refSub == null)
{
Structure attachTarget = Structure.GetAttachTarget(newConnection.Item.WorldPosition);
if (attachTarget == null) { continue; }
refSub = attachTarget.Submarine;
}
Vector2 nodePos = refSub == null ?
newConnection.Item.Position :
newConnection.Item.Position - refSub.HiddenSubPosition;
if (nodes.Count > 0 && nodes[0] == nodePos) { break; }
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) { break; }
//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 (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);
}
break;
}
SetConnectedDirty();
if (connections[0] != null && connections[1] != null)
{
foreach (ItemComponent ic in item.Components)
{
if (ic == this) continue;
ic.Drop(null);
}
if (item.Container != null) item.Container.RemoveContained(this.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 only one end has been connected
IsActive = connections[0] == null ^ connections[1] == null;
}
Drawable = IsActive || nodes.Any();
UpdateSections();
return true;
}
public override void Equip(Character character)
{
ClearConnections(character);
IsActive = true;
}
public override void Unequip(Character character)
{
ClearConnections(character);
IsActive = false;
}
public override void Drop(Character dropper)
{
ClearConnections(dropper);
IsActive = false;
}
public override void Update(float deltaTime, Camera cam)
{
if (nodes.Count == 0) { return; }
Character user = item.ParentInventory?.Owner as Character;
removeNodeDelay = (user?.SelectedConstruction == null) ? removeNodeDelay - 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)
{
//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 = 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; }
float currLength = 0.0f;
for (int i = 0; i < nodes.Count - 1; i++)
{
currLength += Vector2.Distance(nodes[i], nodes[i + 1]);
}
currLength += Vector2.Distance(nodes[nodes.Count - 1], newNodePos);
if (currLength > MaxLength)
{
Vector2 diff = nodes[nodes.Count - 1] - newNodePos;
Vector2 pullBackDir = diff == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(diff);
user.AnimController.Collider.ApplyForce(pullBackDir * user.Mass * 50.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
user.AnimController.UpdateUseItem(true, user.WorldPosition + pullBackDir * 200.0f);
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
if (currLength > MaxLength * 1.5f)
{
ClearConnections();
#if SERVER
CreateNetworkEvent();
#endif
return;
}
}
}
}
}
else
{
newNodePos = RoundNode(item.Position);
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 new Vector2(
MathUtils.RoundTowardsClosest(user.Position.X + mouseDiff.X, Submarine.GridSize.X),
MathUtils.RoundTowardsClosest(user.Position.Y + mouseDiff.Y, Submarine.GridSize.Y));
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || character != Character.Controlled) { return false; }
if (character.SelectedConstruction != null) { 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 && nodes.Count > 0 && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > 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)
{
GameMain.Client.CreateEntityEvent(item, new object[]
{
NetEntityEvent.Type.ComponentState,
item.GetComponentIndex(this),
nodes.Count
});
}
#endif
}
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 && removeNodeDelay <= 0.0f)
{
nodes.RemoveAt(nodes.Count - 1);
UpdateSections();
#if CLIENT
if (GameMain.NetworkMember != null)
{
GameMain.Client.CreateEntityEvent(item, new object[]
{
NetEntityEvent.Type.ComponentState,
item.GetComponentIndex(this),
nodes.Count
});
}
#endif
}
removeNodeDelay = 0.1f;
Drawable = IsActive || sections.Count > 0;
return true;
}
public override bool Pick(Character picker)
{
ClearConnections(picker);
return true;
}
public override void Move(Vector2 amount)
{
#if CLIENT
if (item.IsSelected) MoveNodes(amount);
#endif
}
public List<Vector2> GetNodes()
{
return new List<Vector2>(nodes);
}
public void SetNodes(List<Vector2> nodes)
{
this.nodes = new List<Vector2>(nodes);
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;
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);
}
}
}
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))
{
#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(user.LogName + " 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(user.LogName + " disconnected a wire from " +
connections[0].Item.Name + " (" + connections[0].Name + ")", ServerLog.MessageType.ItemInteraction);
}
else if (connections[1] != null)
{
GameServer.Log(user.LogName + " 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; }
int wireIndex = connections[i].FindWireIndex(item);
if (wireIndex == -1) { continue; }
#if SERVER
if (!connections[i].Item.Removed)
{
connections[i].Item.CreateServerEvent(connections[i].Item.GetComponent<ConnectionPanel>());
}
#endif
connections[i].SetWire(wireIndex, null);
connections[i] = null;
}
Drawable = sections.Count > 0;
}
private Vector2 RoundNode(Vector2 position)
{
position.X = MathUtils.Round(position.X, Submarine.GridSize.X / 2.0f);
position.Y = MathUtils.Round(position.Y, Submarine.GridSize.Y / 2.0f);
return position;
}
public void SetConnectedDirty()
{
for (int i = 0; i < 2; i++)
{
if (connections[i]?.Item != null)
{
var pt = connections[i].Item.GetComponent<PowerTransfer>();
if (pt != null) pt.SetConnectionDirty(connections[i]);
}
}
}
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);
}
private void FixNodeEnds()
{
if (connections[0] == null || connections[1] == null || nodes.Count == 0) { return; }
Vector2 nodePos = nodes[0];
Submarine refSub = connections[0].Item.Submarine ?? connections[1].Item.Submarine;
if (refSub != null) { nodePos += refSub.HiddenSubPosition; }
float dist1 = Vector2.DistanceSquared(connections[0].Item.Position, nodePos);
float dist2 = Vector2.DistanceSquared(connections[1].Item.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;
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.LineToPointDistance(nodes[i], nodes[i + 1], mousePos);
if (dist > maxDist) continue;
if (closestIndex == -1 || dist < closestDist)
{
closestIndex = i;
closestDist = dist;
}
}
}
return closestIndex;
}
public override void FlipX(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(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 override void Load(XElement componentElement, bool usePrefabValues)
{
base.Load(componentElement, usePrefabValues);
string nodeString = componentElement.GetAttributeString("nodes", "");
if (nodeString == "") return;
string[] nodeCoords = nodeString.Split(';');
for (int i = 0; i < nodeCoords.Length / 2; i++)
{
float.TryParse(nodeCoords[i * 2], NumberStyles.Float, CultureInfo.InvariantCulture, out float x);
float.TryParse(nodeCoords[i * 2 + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out float y);
nodes.Add(new Vector2(x, y));
}
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()
{
ClearConnections();
base.RemoveComponentSpecific();
#if CLIENT
overrideSprite?.Remove();
overrideSprite = null;
wireSprite = null;
#endif
}
}
}
@@ -0,0 +1,28 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class XorComponent : AndComponent
{
public XorComponent(Item item, XElement element)
: base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
int sendOutput = 0;
for (int i = 0; i < timeSinceReceived.Length; i++)
{
if (timeSinceReceived[i] <= timeFrame) sendOutput += 1;
timeSinceReceived[i] += deltaTime;
}
string signalOut = sendOutput == 1 ? output : falseOutput;
if (string.IsNullOrEmpty(signalOut)) return;
item.SendSignal(0, signalOut, "signal_out", null);
}
}
}