v1.6.17.0 (Unto the Breach update)
This commit is contained in:
+111
-66
@@ -1,9 +1,9 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -11,24 +11,32 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
[Editable, Serialize(new string[0], IsPropertySaveable.Yes, description: "Signals sent when the corresponding buttons are pressed.", alwaysUseInstanceValues: true)]
|
||||
public string[] Signals { get; set; }
|
||||
|
||||
[Editable, Serialize("", IsPropertySaveable.Yes, description: "Identifiers or tags of items that, when contained, allow the terminal buttons to be used. Multiple ones should be separated by commas.", alwaysUseInstanceValues: true)]
|
||||
public string ActivatingItems { get; set; }
|
||||
|
||||
private int RequiredSignalCount { get; set; }
|
||||
|
||||
private readonly int requiredSignalCount;
|
||||
private ItemContainer Container { get; set; }
|
||||
private HashSet<ItemPrefab> ActivatingItemPrefabs { get; set; } = new HashSet<ItemPrefab>();
|
||||
|
||||
|
||||
private bool AllowUsingButtons => ActivatingItemPrefabs.None() || (Container != null && Container.Inventory.AllItems.Any(i => i != null && ActivatingItemPrefabs.Any(p => p == i.Prefab)));
|
||||
private bool IsActivated => ActivatingItemPrefabs.None() || (Container != null && Container.Inventory.AllItems.Any(i => i != null && ActivatingItemPrefabs.Any(p => p == i.Prefab)));
|
||||
|
||||
private readonly IReadOnlyList<string> buttonSignalDefinitions;
|
||||
|
||||
public ButtonTerminal(Item item, ContentXElement element) : base(item, element)
|
||||
{
|
||||
RequiredSignalCount = element.GetChildElements("TerminalButton").Count(c => c.GetAttribute("style") != null);
|
||||
if (RequiredSignalCount < 1)
|
||||
var buttons = element.GetChildElements("TerminalButton").Where(c => c.GetAttribute("style") != null);
|
||||
if (buttons.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\": no TerminalButton elements defined for the ButtonTerminal component!",
|
||||
contentPackage: element.ContentPackage);
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\": no TerminalButton elements with a style defined for the ButtonTerminal component!", contentPackage: element.ContentPackage);
|
||||
}
|
||||
requiredSignalCount = buttons.Count();
|
||||
List<string> buttonSignals = new ();
|
||||
foreach (ContentXElement button in buttons)
|
||||
{
|
||||
buttonSignals.Add(button.GetAttributeString("signal", null));
|
||||
}
|
||||
buttonSignalDefinitions = buttonSignals.ToImmutableList();
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
@@ -37,57 +45,10 @@ namespace Barotrauma.Items.Components
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
|
||||
if (Signals == null)
|
||||
{
|
||||
Signals = new string[RequiredSignalCount];
|
||||
for (int i = 0; i < RequiredSignalCount; i++)
|
||||
{
|
||||
Signals[i] = string.Empty;
|
||||
}
|
||||
}
|
||||
else if (Signals.Length != RequiredSignalCount)
|
||||
{
|
||||
string[] newSignals = new string[RequiredSignalCount];
|
||||
if (Signals.Length < RequiredSignalCount)
|
||||
{
|
||||
Signals.CopyTo(newSignals, 0);
|
||||
for (int i = Signals.Length; i < RequiredSignalCount; i++)
|
||||
{
|
||||
newSignals[i] = string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < RequiredSignalCount; i++)
|
||||
{
|
||||
newSignals[i] = Signals[i];
|
||||
}
|
||||
}
|
||||
Signals = newSignals;
|
||||
}
|
||||
|
||||
ActivatingItemPrefabs.Clear();
|
||||
if (!string.IsNullOrEmpty(ActivatingItems))
|
||||
{
|
||||
foreach (var activatingItem in ActivatingItems.Split(','))
|
||||
{
|
||||
if (MapEntityPrefab.Find(null, identifier: activatingItem, showErrorMessages: false) is ItemPrefab prefab)
|
||||
{
|
||||
ActivatingItemPrefabs.Add(prefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPrefab.Prefabs.Where(p => p.Tags.Any(t => t == activatingItem))
|
||||
.ForEach(p => ActivatingItemPrefabs.Add(p));
|
||||
}
|
||||
}
|
||||
if (ActivatingItemPrefabs.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\": no activating item prefabs found with identifiers or tags \"{ActivatingItems}\"");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LoadSignals();
|
||||
LoadActivatingItems();
|
||||
|
||||
var containers = item.GetComponents<ItemContainer>();
|
||||
if (containers.Count() != 1)
|
||||
{
|
||||
@@ -97,16 +58,100 @@ namespace Barotrauma.Items.Components
|
||||
Container = containers.FirstOrDefault();
|
||||
|
||||
OnItemLoadedProjSpecific();
|
||||
// Set active so that update loop is active and we can send the state_out signal.
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
partial void OnItemLoadedProjSpecific();
|
||||
|
||||
private bool SendSignal(int signalIndex, Character sender, bool isServerMessage = false)
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!isServerMessage && !AllowUsingButtons) { return false; }
|
||||
string signal = Signals[signalIndex];
|
||||
base.Update(deltaTime, cam);
|
||||
item.SendSignal(IsActivated ? "1" : "0", "state_out");
|
||||
}
|
||||
|
||||
private void LoadSignals()
|
||||
{
|
||||
if (Signals == null || Signals.None())
|
||||
{
|
||||
Signals = new string[requiredSignalCount];
|
||||
for (int i = 0; i < requiredSignalCount; i++)
|
||||
{
|
||||
Signals[i] = string.Empty;
|
||||
}
|
||||
// Load signals from the button elements, if defined.
|
||||
for (int i = 0; i < buttonSignalDefinitions.Count; i++)
|
||||
{
|
||||
Debug.Assert(Signals.Length > i);
|
||||
string overrideDefinition = buttonSignalDefinitions[i];
|
||||
if (overrideDefinition != null)
|
||||
{
|
||||
Signals[i] = overrideDefinition;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Signals.Length != requiredSignalCount)
|
||||
{
|
||||
string[] newSignals = new string[requiredSignalCount];
|
||||
if (Signals.Length < requiredSignalCount)
|
||||
{
|
||||
Signals.CopyTo(newSignals, 0);
|
||||
for (int i = Signals.Length; i < requiredSignalCount; i++)
|
||||
{
|
||||
newSignals[i] = string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < requiredSignalCount; i++)
|
||||
{
|
||||
newSignals[i] = Signals[i];
|
||||
}
|
||||
}
|
||||
Signals = newSignals;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadActivatingItems()
|
||||
{
|
||||
ActivatingItemPrefabs.Clear();
|
||||
if (!string.IsNullOrEmpty(ActivatingItems))
|
||||
{
|
||||
foreach (string activatingItem in ActivatingItems.Split(','))
|
||||
{
|
||||
Identifier itemIdentifier = activatingItem.ToIdentifier();
|
||||
if (MapEntityPrefab.FindByIdentifier(itemIdentifier) is ItemPrefab prefab)
|
||||
{
|
||||
ActivatingItemPrefabs.Add(prefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPrefab.Prefabs.Where(p => p.Tags.Any(t => t == itemIdentifier))
|
||||
.ForEach(p => ActivatingItemPrefabs.Add(p));
|
||||
}
|
||||
}
|
||||
if (ActivatingItemPrefabs.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\": no activating item prefabs found with identifiers or tags \"{ActivatingItems}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
Signals = null;
|
||||
LoadSignals();
|
||||
LoadActivatingItems();
|
||||
}
|
||||
|
||||
private bool SendSignal(int signalIndex, Character sender, bool ignoreState = false, string overrideSignal = null)
|
||||
{
|
||||
if (!ignoreState && !IsActivated) { return false; }
|
||||
string signal = overrideSignal ?? Signals[signalIndex];
|
||||
string connectionName = $"signal_out{signalIndex + 1}";
|
||||
item.SendSignal(new Signal(signal, sender: sender), connectionName);
|
||||
AchievementManager.OnButtonTerminalSignal(item, sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -82,8 +82,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool IsFull => ComponentContainer?.Inventory is { } inventory && inventory.IsFull(true);
|
||||
|
||||
/// <summary>
|
||||
/// Works the same way as the Locked property, but isn't persistent.
|
||||
/// </summary>
|
||||
public bool TemporarilyLocked;
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Locked circuit boxes can only be viewed and not interacted with.")]
|
||||
public bool Locked { get; set; }
|
||||
public bool Locked { get; private set; }
|
||||
|
||||
public CircuitBox(Item item, ContentXElement element) : base(item, element)
|
||||
{
|
||||
@@ -756,6 +761,8 @@ namespace Barotrauma.Items.Components
|
||||
_ => true
|
||||
};
|
||||
|
||||
public bool IsLocked() => Locked || TemporarilyLocked;
|
||||
|
||||
public static Option<Item> GetApplicableResourcePlayerHas(ItemPrefab prefab, Character? character)
|
||||
{
|
||||
if (character is null) { return Option.None; }
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace Barotrauma.Items.Components
|
||||
if (DisplayName.IsNullOrEmpty())
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Missing display name in connection " + item.Name + ": " + Name);
|
||||
DebugConsole.ThrowError($"Could not find a display name for the connection {Name} in the item {item.Name} (submarine: {item.Submarine?.Info?.Name ?? "none"})");
|
||||
#endif
|
||||
DisplayName = Name;
|
||||
}
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for signal components that can select between input/output connections (e.g. multiplexer and demultiplexer components)
|
||||
/// </summary>
|
||||
abstract class ConnectionSelectorComponent : ItemComponent
|
||||
{
|
||||
protected int selectedConnectionIndex;
|
||||
protected string selectedConnectionIndexStr;
|
||||
protected string selectedConnectionName;
|
||||
|
||||
private int connectionCount = -1;
|
||||
|
||||
[InGameEditable,
|
||||
Serialize(0, IsPropertySaveable.Yes, description: "The index of the selected connection.", alwaysUseInstanceValues: true)]
|
||||
public int SelectedConnection
|
||||
{
|
||||
get { return selectedConnectionIndex; }
|
||||
set
|
||||
{
|
||||
selectedConnectionIndex = Math.Max(0, value);
|
||||
//don't clamp until we've determined how many connections the item has
|
||||
//(can't be done until the connection panel component has been loaded too)
|
||||
if (connectionCount > -1)
|
||||
{
|
||||
selectedConnectionIndex = Math.Min(selectedConnectionIndex, connectionCount - 1);
|
||||
}
|
||||
selectedConnectionName = GetConnectionName(selectedConnectionIndex);
|
||||
selectedConnectionIndexStr = selectedConnectionIndex.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable,
|
||||
Serialize(true, IsPropertySaveable.Yes, description: "Should the selected connection go back to the first one when moving past the last one?", alwaysUseInstanceValues: true)]
|
||||
public bool WrapAround
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[InGameEditable,
|
||||
Serialize(true, IsPropertySaveable.Yes, description: "Should empty connections (connections with no wires in them) be skipped over when moving the selection?", alwaysUseInstanceValues: true)]
|
||||
public bool SkipEmptyConnections
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public ConnectionSelectorComponent(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
}
|
||||
|
||||
protected abstract string GetConnectionName(int connectionIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Name of the input connection that sets the selected connection.
|
||||
/// </summary>
|
||||
protected abstract string InputNameSetConnection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the input connection that moves the selected connection.
|
||||
/// </summary>
|
||||
protected abstract string InputNameMoveInput { get; }
|
||||
|
||||
protected abstract IEnumerable<Connection> GetConnections();
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
connectionCount = GetConnections().Count();
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
if (connection.Name == InputNameSetConnection)
|
||||
{
|
||||
if (int.TryParse(signal.value, out int newInput))
|
||||
{
|
||||
SelectedConnection = newInput;
|
||||
}
|
||||
|
||||
}
|
||||
else if (connection.Name == InputNameMoveInput)
|
||||
{
|
||||
if (int.TryParse(signal.value, out int moveAmount))
|
||||
{
|
||||
if (SkipEmptyConnections)
|
||||
{
|
||||
for (int i = 0; i < connectionCount; i++)
|
||||
{
|
||||
moveInput(moveAmount);
|
||||
if (item.Connections.Any(c =>
|
||||
c.Name == selectedConnectionName &&
|
||||
(c.Wires.Any() || c.CircuitBoxConnections.Any())))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
moveInput(moveAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
void moveInput(int moveAmount)
|
||||
{
|
||||
if (WrapAround)
|
||||
{
|
||||
SelectedConnection = MathUtils.PositiveModulo(selectedConnectionIndex + moveAmount, connectionCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedConnection += moveAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+124
-92
@@ -21,6 +21,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
class CustomInterfaceElement : ISerializableEntity
|
||||
{
|
||||
public enum InputTypeOption
|
||||
{
|
||||
Number,
|
||||
Text,
|
||||
Button,
|
||||
TickBox
|
||||
}
|
||||
|
||||
public bool ContinuousSignal;
|
||||
public bool State;
|
||||
public string ConnectionName;
|
||||
@@ -33,6 +41,7 @@ namespace Barotrauma.Items.Components
|
||||
public string Signal { get; set; }
|
||||
|
||||
public Identifier PropertyName { get; }
|
||||
public Identifier TargetItemComponent { get; }
|
||||
public bool TargetOnlyParentProperty { get; }
|
||||
|
||||
public string NumberInputMin { get; }
|
||||
@@ -44,11 +53,21 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public const string DefaultNumberInputMin = "0", DefaultNumberInputMax = "99", DefaultNumberInputStep = "1";
|
||||
public const int DefaultNumberInputDecimalPlaces = 0;
|
||||
public bool IsNumberInput { get; }
|
||||
public InputTypeOption InputType { get; }
|
||||
public NumberType? NumberType { get; }
|
||||
public bool HasPropertyName { get; }
|
||||
public bool ShouldSetProperty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// By default, the elements in the interface only set values of the item or send signals.
|
||||
/// This can be used to make them additionally work the other way around, periodically getting the current value of the property from the item and refreshing the UI.
|
||||
/// </summary>
|
||||
public float GetValueInterval { get; set; } = -1.0f;
|
||||
|
||||
#if CLIENT
|
||||
public float GetValueTimer;
|
||||
#endif
|
||||
|
||||
public string Name => "CustomInterfaceElement";
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; set; }
|
||||
@@ -59,24 +78,26 @@ namespace Barotrauma.Items.Components
|
||||
/// Pass the parent component to the constructor to access the serializable properties
|
||||
/// for elements which change property values.
|
||||
/// </summary>
|
||||
public CustomInterfaceElement(Item item, ContentXElement element, CustomInterface parent)
|
||||
public CustomInterfaceElement(Item item, ContentXElement element, CustomInterface parent, InputTypeOption inputType)
|
||||
{
|
||||
Label = element.GetAttributeString("text", "");
|
||||
ConnectionName = element.GetAttributeString("connection", "");
|
||||
PropertyName = element.GetAttributeIdentifier("propertyname", "");
|
||||
PropertyName = element.GetAttributeIdentifier("propertyname", Identifier.Empty);
|
||||
TargetItemComponent = element.GetAttributeIdentifier("targetitemcomponent", Identifier.Empty);
|
||||
TargetOnlyParentProperty = element.GetAttributeBool("targetonlyparentproperty", false);
|
||||
NumberInputMin = element.GetAttributeString("min", DefaultNumberInputMin);
|
||||
NumberInputMax = element.GetAttributeString("max", DefaultNumberInputMax);
|
||||
NumberInputStep = element.GetAttributeString("step", DefaultNumberInputStep);
|
||||
NumberInputDecimalPlaces = element.GetAttributeInt("decimalplaces", DefaultNumberInputDecimalPlaces);
|
||||
MaxTextLength = element.GetAttributeInt("maxtextlength", int.MaxValue);
|
||||
GetValueInterval = element.GetAttributeFloat(nameof(GetValueInterval), -1.0f);
|
||||
|
||||
InputType = inputType;
|
||||
|
||||
HasPropertyName = !PropertyName.IsEmpty;
|
||||
if (HasPropertyName)
|
||||
{
|
||||
string elementName = element.Name.ToString().ToLowerInvariant();
|
||||
IsNumberInput = elementName == "numberinput" || elementName == "integerinput"; // backwards compatibility
|
||||
if (IsNumberInput)
|
||||
if (inputType == InputTypeOption.Number)
|
||||
{
|
||||
string numberType = element.GetAttributeString("numbertype", string.Empty);
|
||||
switch (numberType)
|
||||
@@ -101,22 +122,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (HasPropertyName && parent != null)
|
||||
{
|
||||
if (TargetOnlyParentProperty)
|
||||
{
|
||||
if (parent.SerializableProperties.ContainsKey(PropertyName))
|
||||
{
|
||||
Signal = parent.SerializableProperties[PropertyName].GetValue(parent) as string;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (ISerializableEntity e in parent.item.AllPropertyObjects)
|
||||
{
|
||||
if (!e.SerializableProperties.ContainsKey(PropertyName)) { continue; }
|
||||
Signal = e.SerializableProperties[PropertyName].GetValue(e) as string;
|
||||
break;
|
||||
}
|
||||
}
|
||||
parent.SetSignalToPropertyValue(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -125,7 +131,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("statuseffect", System.StringComparison.OrdinalIgnoreCase))
|
||||
if (subElement.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName: "custom interface element (label " + Label + ")"));
|
||||
}
|
||||
@@ -193,6 +199,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool ShowInsufficientPowerWarning
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private readonly List<CustomInterfaceElement> customInterfaceElementList = new List<CustomInterfaceElement>();
|
||||
|
||||
public CustomInterface(Item item, ContentXElement element)
|
||||
@@ -200,36 +213,44 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
bool continuousSignalByDefault = false;
|
||||
CustomInterfaceElement.InputTypeOption inputType = CustomInterfaceElement.InputTypeOption.Number;
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "button":
|
||||
inputType = CustomInterfaceElement.InputTypeOption.Button;
|
||||
continuousSignalByDefault = false;
|
||||
break;
|
||||
case "textbox":
|
||||
inputType = CustomInterfaceElement.InputTypeOption.Text;
|
||||
continuousSignalByDefault = false;
|
||||
break;
|
||||
case "integerinput": // backwards compatibility
|
||||
case "numberinput":
|
||||
var button = new CustomInterfaceElement(item, subElement, this)
|
||||
{
|
||||
ContinuousSignal = false
|
||||
};
|
||||
if (string.IsNullOrEmpty(button.Label))
|
||||
{
|
||||
button.Label = "Signal out " + customInterfaceElementList.Count(e => !e.ContinuousSignal);
|
||||
}
|
||||
customInterfaceElementList.Add(button);
|
||||
inputType = CustomInterfaceElement.InputTypeOption.Number;
|
||||
continuousSignalByDefault = false;
|
||||
break;
|
||||
case "tickbox":
|
||||
var tickBox = new CustomInterfaceElement(item, subElement, this)
|
||||
{
|
||||
ContinuousSignal = true
|
||||
};
|
||||
if (string.IsNullOrEmpty(tickBox.Label))
|
||||
{
|
||||
tickBox.Label = "Signal out " + customInterfaceElementList.Count(e => e.ContinuousSignal);
|
||||
}
|
||||
customInterfaceElementList.Add(tickBox);
|
||||
inputType = CustomInterfaceElement.InputTypeOption.TickBox;
|
||||
//the default behavior of tickboxes is different for mainly backwards compatibility reasons
|
||||
//(e.g. keeps sending a true/false signal depending on the state of the tickbox, while the others send a signal when the value changes)
|
||||
continuousSignalByDefault = true;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
var ciElement = new CustomInterfaceElement(item, subElement, this, inputType)
|
||||
{
|
||||
ContinuousSignal = subElement.GetAttributeBool(nameof(CustomInterfaceElement.ContinuousSignal), def: continuousSignalByDefault)
|
||||
};
|
||||
if (string.IsNullOrEmpty(ciElement.Label))
|
||||
{
|
||||
ciElement.Label = "Signal out " + customInterfaceElementList.Count(e => e.ContinuousSignal == ciElement.ContinuousSignal);
|
||||
}
|
||||
customInterfaceElementList.Add(ciElement);
|
||||
IsActive |= ciElement.ContinuousSignal;
|
||||
}
|
||||
IsActive = true;
|
||||
|
||||
InitProjSpecific();
|
||||
//load these here to ensure the UI elements (created in InitProjSpecific) are up-to-date
|
||||
Labels = element.GetAttributeString("labels", "");
|
||||
@@ -268,27 +289,55 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (element.HasPropertyName && element.ShouldSetProperty)
|
||||
{
|
||||
if (element.TargetOnlyParentProperty)
|
||||
{
|
||||
if (SerializableProperties.ContainsKey(element.PropertyName))
|
||||
{
|
||||
SerializableProperties[element.PropertyName].TrySetValue(this, element.Signal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var po in item.AllPropertyObjects)
|
||||
{
|
||||
if (!po.SerializableProperties.ContainsKey(element.PropertyName)) { continue; }
|
||||
po.SerializableProperties[element.PropertyName].TrySetValue(po, element.Signal);
|
||||
}
|
||||
}
|
||||
SetPropertyValueToSignal(element);
|
||||
customInterfaceElementList[i].ShouldSetProperty = false;
|
||||
}
|
||||
}
|
||||
UpdateSignalsProjSpecific();
|
||||
}
|
||||
|
||||
private void SetPropertyValueToSignal(CustomInterfaceElement element)
|
||||
{
|
||||
if (element.TargetOnlyParentProperty)
|
||||
{
|
||||
if (SerializableProperties.ContainsKey(element.PropertyName))
|
||||
{
|
||||
SerializableProperties[element.PropertyName].TrySetValue(this, element.Signal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var po in item.AllPropertyObjects)
|
||||
{
|
||||
if (!po.SerializableProperties.ContainsKey(element.PropertyName)) { continue; }
|
||||
if (!element.TargetItemComponent.IsEmpty && po.Name != element.TargetItemComponent) { continue; }
|
||||
po.SerializableProperties[element.PropertyName].TrySetValue(po, element.Signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSignalToPropertyValue(CustomInterfaceElement element)
|
||||
{
|
||||
if (element.TargetOnlyParentProperty)
|
||||
{
|
||||
if (SerializableProperties.ContainsKey(element.PropertyName))
|
||||
{
|
||||
element.Signal = SerializableProperties[element.PropertyName].GetValue(this)?.ToString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (ISerializableEntity e in item.AllPropertyObjects)
|
||||
{
|
||||
if (!e.SerializableProperties.ContainsKey(element.PropertyName)) { continue; }
|
||||
if (!element.TargetItemComponent.IsEmpty && e.Name != element.TargetItemComponent) { continue; }
|
||||
element.Signal = e.SerializableProperties[element.PropertyName].GetValue(e)?.ToString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
|
||||
@@ -333,41 +382,28 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (tickBoxElement == null) { return; }
|
||||
tickBoxElement.State = state;
|
||||
tickBoxElement.Signal = state.ToString();
|
||||
if (!tickBoxElement.ContinuousSignal)
|
||||
{
|
||||
SetPropertyValueToSignal(tickBoxElement);
|
||||
}
|
||||
}
|
||||
|
||||
private void TextChanged(CustomInterfaceElement textElement, string text)
|
||||
{
|
||||
if (textElement == null) { return; }
|
||||
textElement.Signal = text;
|
||||
if (!textElement.TargetOnlyParentProperty)
|
||||
{
|
||||
foreach (ISerializableEntity e in item.AllPropertyObjects)
|
||||
{
|
||||
if (!e.SerializableProperties.ContainsKey(textElement.PropertyName)) { continue; }
|
||||
e.SerializableProperties[textElement.PropertyName].TrySetValue(e, text);
|
||||
}
|
||||
}
|
||||
else if (SerializableProperties.ContainsKey(textElement.PropertyName))
|
||||
{
|
||||
SerializableProperties[textElement.PropertyName].TrySetValue(this, text);
|
||||
}
|
||||
SetPropertyValueToSignal(textElement);
|
||||
}
|
||||
|
||||
private void ValueChanged(CustomInterfaceElement numberInputElement, int value)
|
||||
{
|
||||
if (numberInputElement == null) { return; }
|
||||
numberInputElement.Signal = value.ToString();
|
||||
if (!numberInputElement.TargetOnlyParentProperty)
|
||||
SetPropertyValueToSignal(numberInputElement);
|
||||
foreach (StatusEffect effect in numberInputElement.StatusEffects)
|
||||
{
|
||||
foreach (ISerializableEntity e in item.AllPropertyObjects)
|
||||
{
|
||||
if (!e.SerializableProperties.ContainsKey(numberInputElement.PropertyName)) { continue; }
|
||||
e.SerializableProperties[numberInputElement.PropertyName].TrySetValue(e, value);
|
||||
}
|
||||
}
|
||||
else if (SerializableProperties.ContainsKey(numberInputElement.PropertyName))
|
||||
{
|
||||
SerializableProperties[numberInputElement.PropertyName].TrySetValue(this, value);
|
||||
item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, character: item.ParentInventory?.Owner as Character);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,25 +411,14 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (numberInputElement == null) { return; }
|
||||
numberInputElement.Signal = value.ToString();
|
||||
if (!numberInputElement.TargetOnlyParentProperty)
|
||||
{
|
||||
foreach (ISerializableEntity e in item.AllPropertyObjects)
|
||||
{
|
||||
if (!e.SerializableProperties.ContainsKey(numberInputElement.PropertyName)) { continue; }
|
||||
e.SerializableProperties[numberInputElement.PropertyName].TrySetValue(e, value);
|
||||
}
|
||||
}
|
||||
else if (SerializableProperties.ContainsKey(numberInputElement.PropertyName))
|
||||
{
|
||||
SerializableProperties[numberInputElement.PropertyName].TrySetValue(this, value);
|
||||
}
|
||||
SetPropertyValueToSignal(numberInputElement);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
|
||||
{
|
||||
if (!ciElement.ContinuousSignal) { continue; }
|
||||
if (!ciElement.ContinuousSignal && ciElement.PropertyName != "Voltage") { continue; }
|
||||
//TODO: allow changing output when a tickbox is not selected
|
||||
if (!string.IsNullOrEmpty(ciElement.Signal) && ciElement.Connection != null)
|
||||
{
|
||||
@@ -407,6 +432,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
//CustomInterface works even when broken (it should be possible to tick the checkboxes and change values,
|
||||
//it's up to the other components to work or not work depending on whether the item is broken)
|
||||
Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
{
|
||||
labels = customInterfaceElementList.Select(ci => ci.Label).ToArray();
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A component with one input and multiple outputs. Can be used to choose which output the signal should be passed to.
|
||||
/// </summary>
|
||||
sealed class DemultiplexerComponent : ConnectionSelectorComponent
|
||||
{
|
||||
public DemultiplexerComponent(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override string InputNameSetConnection => "set_output";
|
||||
|
||||
protected override string InputNameMoveInput => "move_output";
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
IsActive = item.Connections != null && item.Connections.Any(c => c.Name == "selected_output_out");
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
if (connection.Name == "signal_in")
|
||||
{
|
||||
item.SendSignal(signal, selectedConnectionName);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ReceiveSignal(signal, connection);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
item.SendSignal(selectedConnectionIndexStr, "selected_output_out");
|
||||
}
|
||||
|
||||
protected override string GetConnectionName(int connectionIndex)
|
||||
{
|
||||
return "signal_out" + connectionIndex;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Connection> GetConnections()
|
||||
{
|
||||
if (item.GetComponent<ConnectionPanel>() is { } connectionPanel)
|
||||
{
|
||||
return connectionPanel.Connections.Where(c => c.IsOutput && c.Name.StartsWith("signal_out"));
|
||||
}
|
||||
return Enumerable.Empty<Connection>();
|
||||
}
|
||||
}
|
||||
@@ -94,9 +94,9 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
if (isOn == value && IsActive == value) { return; }
|
||||
|
||||
IsActive = isOn = value;
|
||||
SetLightSourceState(value, value ? lightBrightness : 0.0f);
|
||||
bool isLightOn = isOn && item.Condition > 0;
|
||||
SetLightSourceState(isLightOn, isLightOn ? lightBrightness : 0.0f);
|
||||
OnStateChanged();
|
||||
}
|
||||
}
|
||||
@@ -259,7 +259,6 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
|
||||
IsActive = IsOn;
|
||||
item.AddTag("light");
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -32,6 +35,14 @@ namespace Barotrauma.Items.Components
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("", IsPropertySaveable.Yes, description: "Does the sensor react only to certain characters (species names, groups or tags)? Doesn't have an effect, if the Target Type is incorrect.", alwaysUseInstanceValues: true)]
|
||||
public string TargetCharacters
|
||||
{
|
||||
get => targetCharacters.ConvertToString();
|
||||
set => targetCharacters = value.ToIdentifiers().ToHashSet();
|
||||
}
|
||||
private HashSet<Identifier> targetCharacters;
|
||||
|
||||
[InGameEditable, Serialize(false, IsPropertySaveable.Yes, description: "Should the sensor ignore the bodies of dead characters?", alwaysUseInstanceValues: true)]
|
||||
public bool IgnoreDead
|
||||
@@ -40,7 +51,6 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[InGameEditable, Serialize(0.0f, IsPropertySaveable.Yes, description: "Horizontal detection range.", alwaysUseInstanceValues: true)]
|
||||
public float RangeX
|
||||
{
|
||||
@@ -259,40 +269,22 @@ namespace Barotrauma.Items.Components
|
||||
bool triggerFromMonsters = Target.HasFlag(TargetType.Monster);
|
||||
bool hasTriggers = triggerFromHumans || triggerFromPets || triggerFromMonsters;
|
||||
if (!hasTriggers) { return; }
|
||||
foreach (Character c in Character.CharacterList)
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (IgnoreDead && c.IsDead) { continue; }
|
||||
|
||||
//ignore characters that have spawned a second or less ago
|
||||
//makes it possible to detect when a spawned character moves without triggering the detector immediately as the ragdoll spawns and drops to the ground
|
||||
if (c.SpawnTime > Timing.TotalTime - 1.0) { continue; }
|
||||
if (c.IsHuman)
|
||||
{
|
||||
if (!triggerFromHumans) { continue; }
|
||||
}
|
||||
else if (c.IsPet)
|
||||
{
|
||||
if (!triggerFromPets) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a human or a pet -> monster?
|
||||
if (!triggerFromMonsters) { continue; }
|
||||
if (CharacterParams.CompareGroup(c.Group, CharacterPrefab.HumanGroup))
|
||||
{
|
||||
//characters in the "human" group aren't considered monsters (even if they were something like a friendly mudraptor)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (character.SpawnTime > Timing.TotalTime - 1.0) { continue; }
|
||||
|
||||
if (!TriggersOn(character)) { 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)
|
||||
if (Math.Abs(character.WorldPosition.X - detectPos.X) > broadRangeX || Math.Abs(character.WorldPosition.Y - detectPos.Y) > broadRangeY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.LinearVelocity.LengthSquared() < MinimumVelocity * MinimumVelocity) { continue; }
|
||||
@@ -304,7 +296,56 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool TriggersOn(Character character)
|
||||
{
|
||||
bool triggerFromHumans = Target.HasFlag(TargetType.Human);
|
||||
bool triggerFromPets = Target.HasFlag(TargetType.Pet);
|
||||
bool triggerFromMonsters = Target.HasFlag(TargetType.Monster);
|
||||
bool hasTriggers = triggerFromHumans || triggerFromPets || triggerFromMonsters;
|
||||
if (!hasTriggers) { return false; }
|
||||
return TriggersOn(character, triggerFromHumans, triggerFromPets, triggerFromMonsters);
|
||||
}
|
||||
|
||||
private bool TriggersOn(Character character, bool triggerFromHumans, bool triggerFromPets, bool triggerFromMonsters)
|
||||
{
|
||||
if (IgnoreDead && character.IsDead) { return false; }
|
||||
if (character.IsHuman)
|
||||
{
|
||||
if (!triggerFromHumans) { return false; }
|
||||
}
|
||||
else if (character.IsPet)
|
||||
{
|
||||
if (!triggerFromPets) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a human or a pet -> monster?
|
||||
if (!triggerFromMonsters) { return false; }
|
||||
if (CharacterParams.CompareGroup(character.Group, CharacterPrefab.HumanGroup))
|
||||
{
|
||||
//characters in the "human" group aren't considered monsters (even if they were something like a friendly mudraptor)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Check matching character, if defined.
|
||||
if (targetCharacters.Any())
|
||||
{
|
||||
// Performance critical code -> using a foreach loop to avoid having to capture variables in lambdas.
|
||||
bool matchFound = false;
|
||||
foreach (Identifier target in targetCharacters)
|
||||
{
|
||||
if (character.MatchesSpeciesNameOrGroup(target) || character.Params.HasTag(target))
|
||||
{
|
||||
matchFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchFound) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
{
|
||||
Vector2 prevDetectOffset = detectOffset;
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A component with multiple inputs and one output. Can be used to choose which input the component passes signals to the output from.
|
||||
/// </summary>
|
||||
sealed class MultiplexerComponent : ConnectionSelectorComponent
|
||||
{
|
||||
public MultiplexerComponent(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string InputNameSetConnection => "set_input";
|
||||
|
||||
protected override string InputNameMoveInput => "move_input";
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
IsActive = item.Connections != null && item.Connections.Any(c => c.Name == "selected_input_out");
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
item.SendSignal(selectedConnectionIndexStr, "selected_input_out");
|
||||
}
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
if (connection.Name.StartsWith("signal_in"))
|
||||
{
|
||||
if (connection.Name == selectedConnectionName)
|
||||
{
|
||||
item.SendSignal(signal, "signal_out");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ReceiveSignal(signal, connection);
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetConnectionName(int connectionIndex)
|
||||
{
|
||||
return "signal_in" + connectionIndex;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Connection> GetConnections()
|
||||
{
|
||||
if (item.GetComponent<ConnectionPanel>() is { } connectionPanel)
|
||||
{
|
||||
return connectionPanel.Connections.Where(c => !c.IsOutput && c.Name.StartsWith("signal_in"));
|
||||
}
|
||||
return Enumerable.Empty<Connection>();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class OxygenDetector : ItemComponent
|
||||
{
|
||||
public const int LowOxygenPercentage = 35;
|
||||
|
||||
private int prevSentOxygenValue;
|
||||
private string oxygenSignal;
|
||||
public string OxygenSignal { get; private set; }
|
||||
|
||||
public OxygenDetector(Item item, ContentXElement element)
|
||||
: base (item, element)
|
||||
@@ -20,13 +18,13 @@ namespace Barotrauma.Items.Components
|
||||
if (item.CurrentHull == null) { return; }
|
||||
|
||||
int currOxygenPercentage = (int)item.CurrentHull.OxygenPercentage;
|
||||
if (prevSentOxygenValue != currOxygenPercentage || oxygenSignal == null)
|
||||
if (prevSentOxygenValue != currOxygenPercentage || OxygenSignal == null)
|
||||
{
|
||||
prevSentOxygenValue = currOxygenPercentage;
|
||||
oxygenSignal = prevSentOxygenValue.ToString();
|
||||
OxygenSignal = prevSentOxygenValue.ToString();
|
||||
}
|
||||
|
||||
item.SendSignal(oxygenSignal, "signal_out");
|
||||
item.SendSignal(OxygenSignal, "signal_out");
|
||||
item.SendSignal(currOxygenPercentage <= LowOxygenPercentage ? "1" : "0", "low_oxygen");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -8,7 +7,7 @@ namespace Barotrauma.Items.Components
|
||||
const float FireCheckInterval = 1.0f;
|
||||
private float fireCheckTimer;
|
||||
|
||||
private bool fireInRange;
|
||||
public bool FireInRange { get; private set; }
|
||||
|
||||
private int maxOutputLength;
|
||||
[Editable, Serialize(200, IsPropertySaveable.No, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")]
|
||||
@@ -80,10 +79,10 @@ namespace Barotrauma.Items.Components
|
||||
fireCheckTimer -= deltaTime;
|
||||
if (fireCheckTimer <= 0.0f)
|
||||
{
|
||||
fireInRange = IsFireInRange();
|
||||
FireInRange = IsFireInRange();
|
||||
fireCheckTimer = FireCheckInterval;
|
||||
}
|
||||
string signalOut = fireInRange ? Output : FalseOutput;
|
||||
string signalOut = FireInRange ? Output : FalseOutput;
|
||||
if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(signalOut, "signal_out"); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ namespace Barotrauma.Items.Components
|
||||
private float editNodeDelay;
|
||||
|
||||
private bool locked;
|
||||
|
||||
public bool Locked
|
||||
{
|
||||
get
|
||||
|
||||
Reference in New Issue
Block a user