v1.6.17.0 (Unto the Breach update)

This commit is contained in:
Regalis11
2024-10-22 17:29:04 +03:00
parent e74b3cdb17
commit 6e6c17e100
417 changed files with 17166 additions and 5870 deletions
@@ -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;
}
@@ -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;
}
}
}
}
@@ -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();
@@ -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;
@@ -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