Unstable 0.17.0.0

This commit is contained in:
Markus Isberg
2022-02-26 02:43:01 +09:00
parent a83f375681
commit 3974067915
913 changed files with 32472 additions and 32364 deletions
@@ -13,8 +13,6 @@ namespace Barotrauma.Items.Components
private float charge;
//private float rechargeVoltage;
//how fast the battery can be recharged
private float maxRechargeSpeed;
@@ -27,45 +25,52 @@ namespace Barotrauma.Items.Components
protected Vector2 indicatorPosition, indicatorSize;
protected bool isHorizontal;
protected override PowerPriority Priority { get { return PowerPriority.Battery; } }
private float currPowerOutput;
public float CurrPowerOutput
{
get;
private set;
get { return currPowerOutput; }
private set
{
System.Diagnostics.Debug.Assert(value >= 0.0f);
currPowerOutput = Math.Max(0, value);
}
}
[Serialize("0,0", true, description: "The position of the progress bar indicating the charge of the item. In pixels as an offset from the upper left corner of the sprite.")]
[Serialize("0,0", IsPropertySaveable.Yes, description: "The position of the progress bar indicating the charge of the item. In pixels as an offset from the upper left corner of the sprite.")]
public Vector2 IndicatorPosition
{
get { return indicatorPosition; }
set { indicatorPosition = value; }
}
[Serialize("0,0", true, description: "The size of the progress bar indicating the charge of the item (in pixels).")]
[Serialize("0,0", IsPropertySaveable.Yes, description: "The size of the progress bar indicating the charge of the item (in pixels).")]
public Vector2 IndicatorSize
{
get { return indicatorSize; }
set { indicatorSize = value; }
}
[Serialize(false, true, description: "Should the progress bar indicating the charge of the item fill up horizontally or vertically.")]
[Serialize(false, IsPropertySaveable.Yes, description: "Should the progress bar indicating the charge of the item fill up horizontally or vertically.")]
public bool IsHorizontal
{
get { return isHorizontal; }
set { isHorizontal = value; }
}
[Editable, Serialize(10.0f, true, description: "Maximum output of the device when fully charged (kW).")]
[Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "Maximum output of the device when fully charged (kW).")]
public float MaxOutPut { set; get; }
[Editable, Serialize(10.0f, true, description: "The maximum capacity of the device (kW * min). For example, a value of 1000 means the device can output 100 kilowatts of power for 10 minutes, or 1000 kilowatts for 1 minute.")]
[Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "The maximum capacity of the device (kW * min). For example, a value of 1000 means the device can output 100 kilowatts of power for 10 minutes, or 1000 kilowatts for 1 minute.")]
public float Capacity
{
get { return capacity; }
set { capacity = Math.Max(value, 1.0f); }
}
[Editable, Serialize(0.0f, true, description: "The current charge of the device.")]
[Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "The current charge of the device.")]
public float Charge
{
get { return charge; }
@@ -87,14 +92,14 @@ namespace Barotrauma.Items.Components
public float ChargePercentage => MathUtils.Percentage(Charge, Capacity);
[Editable, Serialize(10.0f, true, description: "How fast the device can be recharged. For example, a recharge speed of 100 kW and a capacity of 1000 kW*min would mean it takes 10 minutes to fully charge the device.")]
[Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "How fast the device can be recharged. For example, a recharge speed of 100 kW and a capacity of 1000 kW*min would mean it takes 10 minutes to fully charge the device.")]
public float MaxRechargeSpeed
{
get { return maxRechargeSpeed; }
set { maxRechargeSpeed = Math.Max(value, 1.0f); }
}
[Editable, Serialize(10.0f, true, description: "The current recharge speed of the device.")]
[Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "The current recharge speed of the device.")]
public float RechargeSpeed
{
get { return rechargeSpeed; }
@@ -110,14 +115,14 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(false, true, description: "If true, the recharge speed (and power consumption) of the device goes up exponentially as the recharge rate is increased.")]
[Serialize(false, IsPropertySaveable.Yes, description: "If true, the recharge speed (and power consumption) of the device goes up exponentially as the recharge rate is increased.")]
public bool ExponentialRechargeSpeed { get; set; }
[Editable(minValue: 0.0f, maxValue: 10.0f, decimals: 2), Serialize(0.5f, true)]
[Editable(minValue: 0.0f, maxValue: 10.0f, decimals: 2), Serialize(0.5f, IsPropertySaveable.Yes)]
public float RechargeAdjustSpeed { get; set; }
private float efficiency;
[Editable(minValue: 0.0f, maxValue: 1.0f, decimals: 2), Serialize(0.95f, true, description: "The amount of power you can get out of a item relative to the amount of power that's put into it.")]
[Editable(minValue: 0.0f, maxValue: 1.0f, decimals: 2), Serialize(0.95f, IsPropertySaveable.Yes, description: "The amount of power you can get out of a item relative to the amount of power that's put into it.")]
public float Efficiency
{
get { return efficiency; }
@@ -130,7 +135,7 @@ namespace Barotrauma.Items.Components
private bool isRunning;
public bool HasBeenTuned { get; private set; }
public PowerContainer(Item item, XElement element)
public PowerContainer(Item item, ContentXElement element)
: base(item, element)
{
IsActive = true;
@@ -154,92 +159,142 @@ namespace Barotrauma.Items.Components
isRunning = true;
float chargeRatio = charge / capacity;
float gridPower = 0.0f;
float gridLoad = 0.0f;
foreach (Connection c in item.Connections)
{
if (!c.IsPower || !c.IsOutput) { continue; }
foreach (Connection c2 in c.Recipients)
{
if (c2.Item.Condition <= 0.0f) { continue; }
PowerTransfer pt = c2.Item.GetComponent<PowerTransfer>();
if (pt == null)
{
foreach (Powered powered in c2.Item.GetComponents<Powered>())
{
if (!powered.IsActive) continue;
gridLoad += powered.CurrPowerConsumption;
}
continue;
}
if (!pt.IsActive || !pt.CanTransfer) { continue; }
gridPower -= pt.CurrPowerConsumption;
gridLoad += pt.PowerLoad;
}
}
if (chargeRatio > 0.0f)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
}
if (charge >= capacity)
float loadReading = 0;
if (powerOut != null && powerOut.Grid != null)
{
//rechargeVoltage = 0.0f;
charge = capacity;
CurrPowerConsumption = 0.0f;
}
else
{
float missingCharge = capacity - charge;
float targetRechargeSpeed = rechargeSpeed;
if (ExponentialRechargeSpeed)
{
targetRechargeSpeed = MathF.Pow(rechargeSpeed / maxRechargeSpeed, 2) * maxRechargeSpeed;
}
if (missingCharge < 1.0f)
{
targetRechargeSpeed *= missingCharge;
}
if (currPowerConsumption < targetRechargeSpeed)
{
currPowerConsumption = Math.Min(currPowerConsumption + deltaTime * maxRechargeSpeed * RechargeAdjustSpeed, targetRechargeSpeed);
}
else
{
currPowerConsumption = Math.Max(currPowerConsumption - deltaTime * maxRechargeSpeed * RechargeAdjustSpeed, targetRechargeSpeed);
}
Charge += currPowerConsumption * Math.Min(Voltage, 1.0f) / 3600.0f * efficiency;
}
if (charge <= 0.0f)
{
CurrPowerOutput = 0.0f;
charge = 0.0f;
return;
}
else
{
//output starts dropping when the charge is less than 10%
float maxOutputRatio = 1.0f;
if (chargeRatio < 0.1f)
{
maxOutputRatio = Math.Max(chargeRatio * 10.0f, 0.0f);
}
CurrPowerOutput += (gridLoad - gridPower) * deltaTime;
float maxOutput = Math.Min(MaxOutPut * maxOutputRatio, gridLoad);
CurrPowerOutput = MathHelper.Clamp(CurrPowerOutput, 0.0f, maxOutput);
Charge -= CurrPowerOutput / 3600.0f;
loadReading = powerOut.Grid.Load;
}
item.SendSignal(((int)Math.Round(-CurrPowerOutput)).ToString(), "power_value_out");
item.SendSignal(((int)Math.Round(loadReading)).ToString(), "load_value_out");
item.SendSignal(((int)Math.Round(Charge)).ToString(), "charge");
item.SendSignal(((int)Math.Round(Charge / capacity * 100)).ToString(), "charge_%");
item.SendSignal(((int)Math.Round(RechargeSpeed / maxRechargeSpeed * 100)).ToString(), "charge_rate");
}
/// <summary>
/// Returns the power consumption if checking the powerIn connection, or a negative value if the output can provide power when checking powerOut.
/// Power consumption is proportional to set recharge speed and if there is less than max charge.
/// </summary>
public override float GetCurrentPowerConsumption(Connection connection = null)
{
if (connection == powerIn)
{
//Don't draw power if fully charged
if (charge >= capacity)
{
charge = capacity;
return 0;
}
else
{
float missingCharge = capacity - charge;
float targetRechargeSpeed = rechargeSpeed;
if (ExponentialRechargeSpeed)
{
targetRechargeSpeed = MathF.Pow(rechargeSpeed / maxRechargeSpeed, 2) * maxRechargeSpeed;
}
//For the last kwMin scale the recharge rate linearly to prevent overcharging and to have a smooth cutoff
if (missingCharge < 1.0f)
{
targetRechargeSpeed *= missingCharge;
}
return MathHelper.Clamp(targetRechargeSpeed, 0, MaxRechargeSpeed);
}
}
else
{
CurrPowerOutput = 0;
return charge > 0 ? -1 : 0;
}
}
/// <summary>
/// Minimum and maximum output for the queried connection.
/// Powerin min max equals CurrPowerConsumption as its abnormal for there to be power out.
/// PowerOut min power out is zero and max is the maxout unless below 10% charge where
/// the output is scaled relative to the 10% charge.
/// </summary>
/// <param name="connection">Connection being queried</param>
/// <param name="load">Current grid load</param>
/// <returns>Minimum and maximum power output for the connection</returns>
public override PowerRange MinMaxPowerOut(Connection connection, float load = 0)
{
if (connection == powerOut)
{
float maxOutput;
float chargeRatio = charge / capacity;
if (chargeRatio < 0.1f)
{
maxOutput = Math.Max(chargeRatio * 10.0f, 0.0f) * MaxOutPut;
}
else
{
maxOutput = MaxOutPut;
}
//Limit max power out to not exceed the charge of the container
maxOutput = Math.Min(maxOutput, charge * 60 / UpdateInterval);
return new PowerRange(0.0f, maxOutput);
}
return PowerRange.Zero;
}
/// <summary>
/// Finalized power out from the container for the connection, provided the given grid information
/// Output power based on the maxpower all batteries can output. So all batteries can
/// equally share powerout based on their output capabilities.
/// </summary>
/// <param name="connection"></param>
/// <param name="power"></param>
/// <param name="minMaxPower"></param>
/// <param name="load"></param>
/// <returns></returns>
public override float GetConnectionPowerOut(Connection connection, float power, PowerRange minMaxPower, float load)
{
if (connection == powerOut)
{
//Calculate the max power the container can output
float maxPowerOutput = MaxOutPut;
float chargeRatio = charge / capacity;
if (chargeRatio < 0.1f)
{
maxPowerOutput *= Math.Max(chargeRatio * 10.0f, 0.0f);
}
//Set power output based on the relative max power output capabilities and load demand
CurrPowerOutput = MathHelper.Clamp((load - power) / minMaxPower.Max, 0, 1) * maxPowerOutput;
return CurrPowerOutput;
}
return 0.0f;
}
/// <summary>
/// When the corresponding grid connection is resolved, adjust the container's charge.
/// </summary>
public override void GridResolved(Connection conn)
{
if (conn == powerIn)
{
//Increase charge based on how much power came in from the grid
Charge += (CurrPowerConsumption * Voltage) / 60 * UpdateInterval * efficiency;
}
else
{
//Decrease charge based on how much power is leaving the device
Charge = Math.Clamp(Charge - CurrPowerOutput / 60 * UpdateInterval, 0, Capacity);
}
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
@@ -250,8 +305,8 @@ namespace Barotrauma.Items.Components
}
if (HasBeenTuned) { return true; }
float targetRatio = string.IsNullOrEmpty(objective.Option) || objective.Option.Equals("charge", StringComparison.OrdinalIgnoreCase) ? aiRechargeTargetRatio : -1;
if (targetRatio > 0 || float.TryParse(objective.Option, out targetRatio))
float targetRatio = objective.Option.IsEmpty || objective.Option == "charge" ? aiRechargeTargetRatio : -1;
if (targetRatio > 0 || float.TryParse(objective.Option.Value, out targetRatio))
{
if (Math.Abs(rechargeSpeed - maxRechargeSpeed * targetRatio) > 0.05f)
{
@@ -267,9 +322,10 @@ namespace Barotrauma.Items.Components
#endif
if (character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariables("DialogChargeBatteries", new string[2] { "[itemname]", "[rate]" },
new string[2] { item.Name, ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString() },
new bool[2] { true, false }), null, 1.0f, "chargebattery", 10.0f);
character.Speak(TextManager.GetWithVariables("DialogChargeBatteries",
("[itemname]", item.Name, FormatCapitals.Yes),
("[rate]", ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString(), FormatCapitals.No)).Value,
null, 1.0f, "chargebattery".ToIdentifier(), 10.0f);
}
}
}
@@ -289,9 +345,10 @@ namespace Barotrauma.Items.Components
#endif
if (character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariables("DialogStopChargingBatteries", new string[2] { "[itemname]", "[rate]" },
new string[2] { item.Name, ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString() },
new bool[2] { true, false }), null, 1.0f, "chargebattery", 10.0f);
character.Speak(TextManager.GetWithVariables("DialogStopChargingBatteries",
("[itemname]", item.Name, FormatCapitals.Yes),
("[rate]", ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString(), FormatCapitals.No)).Value,
null, 1.0f, "chargebattery".ToIdentifier(), 10.0f);
}
}
}
@@ -26,18 +26,25 @@ namespace Barotrauma.Items.Components
public float PowerLoad
{
get { return powerLoad; }
get
{
if (this is RelayComponent || PowerConnections.Count == 0 || PowerConnections[0].Grid == null)
{
return powerLoad;
}
return PowerConnections[0].Grid.Load;
}
set { powerLoad = value; }
}
[Editable, Serialize(true, true, description: "Can the item be damaged if too much power is supplied to the power grid.")]
[Editable, Serialize(true, IsPropertySaveable.Yes, description: "Can the item be damaged if too much power is supplied to the power grid.")]
public bool CanBeOverloaded
{
get;
set;
}
[Editable(MinValueFloat = 1.0f), Serialize(2.0f, true, description:
[Editable(MinValueFloat = 1.0f), Serialize(2.0f, IsPropertySaveable.Yes, description:
"How much power has to be supplied to the grid relative to the load before item starts taking damage. "
+ "E.g. a value of 2 means that the grid has to be receiving twice as much power as the devices in the grid are consuming.")]
public float OverloadVoltage
@@ -46,14 +53,14 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(0.15f, true, description: "The probability for a fire to start when the item breaks."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
[Serialize(0.15f, IsPropertySaveable.Yes, description: "The probability for a fire to start when the item breaks."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
public float FireProbability
{
get;
set;
}
[Serialize(false, false, description: "Is the item currently overloaded. Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
[Serialize(false, IsPropertySaveable.No, description: "Is the item currently overloaded. Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
public bool Overload
{
get;
@@ -62,6 +69,7 @@ namespace Barotrauma.Items.Components
private float extraLoad;
private float extraLoadSetTime;
/// <summary>
/// Additional load coming from somewhere else than the devices connected to the junction box (e.g. ballast flora or piezo crystals).
/// Goes back to zero automatically if you stop setting the value.
@@ -71,7 +79,7 @@ namespace Barotrauma.Items.Components
get { return extraLoad; }
set
{
extraLoad = Math.Max(value, 0.0f);
extraLoad = value;
extraLoadSetTime = (float)Timing.TotalTime;
}
}
@@ -112,7 +120,7 @@ namespace Barotrauma.Items.Components
}
}
public PowerTransfer(Item item, XElement element)
public PowerTransfer(Item item, ContentXElement element)
: base(item, element)
{
IsActive = true;
@@ -168,9 +176,34 @@ namespace Barotrauma.Items.Components
{
RefreshConnections();
float powerReadingOut = 0;
float loadReadingOut = ExtraLoad;
if (powerLoad < 0)
{
powerReadingOut = -powerLoad;
loadReadingOut = 0;
}
if (powerOut != null && powerOut.Grid != null)
{
powerReadingOut = powerOut.Grid.Power;
loadReadingOut = powerOut.Grid.Load;
}
item.SendSignal(((int)Math.Round(powerReadingOut)).ToString(), "power_value_out");
item.SendSignal(((int)Math.Round(loadReadingOut)).ToString(), "load_value_out");
if (Timing.TotalTime > extraLoadSetTime + 1.0)
{
extraLoad = Math.Max(extraLoad - 1000.0f * deltaTime, 0);
//Decay the extra load to 0 from either positive or negative
if (extraLoad > 0)
{
extraLoad = Math.Max(extraLoad - 1000.0f * deltaTime, 0);
}
else
{
extraLoad = Math.Min(extraLoad + 1000.0f * deltaTime, 0);
}
}
if (!CanTransfer) { return; }
@@ -200,7 +233,9 @@ namespace Barotrauma.Items.Components
item.SendSignal(loadSignal, "load_value_out");
float maxOverVoltage = Math.Max(OverloadVoltage, 1.0f);
Overload = -currPowerConsumption > Math.Max(powerLoad, 200.0f) * maxOverVoltage;
Overload = Voltage > maxOverVoltage;
if (Overload && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
{
if (overloadCooldownTimer > 0.0f)
@@ -239,6 +274,11 @@ namespace Barotrauma.Items.Components
}
}
public override float GetConnectionPowerOut(Connection conn, float power, PowerRange minMaxPower, float load)
{
return conn == powerOut ? PowerConsumption + ExtraLoad : 0;
}
public override bool Pick(Character picker)
{
return picker != null;
@@ -376,25 +416,6 @@ namespace Barotrauma.Items.Components
SetAllConnectionsDirty();
}
public override void ReceivePowerProbeSignal(Connection connection, Item source, float power)
{
//we've already received this signal
if (lastPowerProbeRecipients.Contains(this)) { return; }
if (item.Condition <= 0.0f) { return; }
lastPowerProbeRecipients.Add(this);
if (power < 0.0f)
{
powerLoad -= power;
}
else
{
currPowerConsumption -= power;
}
powerOut?.SendPowerProbeSignal(source, power);
}
public override void ReceiveSignal(Signal signal, Connection connection)
{
if (item.Condition <= 0.0f || connection.IsPower) { return; }
@@ -8,10 +8,57 @@ using Barotrauma.Sounds;
namespace Barotrauma.Items.Components
{
/// <summary>
/// Order in which power sources will provide to a grid, lower number is higher priority
/// </summary>
public enum PowerPriority
{
Default = 0, // Use for status effects and/or extraload
Reactor = 1,
Relay = 3,
Battery = 5
}
readonly struct PowerRange
{
public readonly static PowerRange Zero = default;
public readonly float Min;
public readonly float Max;
/// <summary>
/// Used by reactors to communicate their maximum output to each other so they can divide the grid load between each other in a sensible way
/// </summary>
public readonly float ReactorMaxOutput;
public PowerRange(float min, float max) : this(min, max, 0.0f)
{
}
public PowerRange(float min, float max, float reactorMaxOutput)
{
System.Diagnostics.Debug.Assert(max >= min);
System.Diagnostics.Debug.Assert(min >= 0);
System.Diagnostics.Debug.Assert(max >= 0);
Min = min;
Max = max;
ReactorMaxOutput = reactorMaxOutput;
}
public static PowerRange operator +(PowerRange a, PowerRange b)
{
return new PowerRange(a.Min + b.Min, a.Max + b.Max, a.ReactorMaxOutput + b.ReactorMaxOutput);
}
public static PowerRange operator -(PowerRange a, PowerRange b)
{
return new PowerRange(a.Min - b.Min, a.Max - b.Max, a.ReactorMaxOutput - b.ReactorMaxOutput);
}
}
partial class Powered : ItemComponent
{
private static float updateTimer;
protected static float UpdateInterval = 0.2f;
//TODO: test sparser update intervals?
protected const float UpdateInterval = (float)Timing.Step;
/// <summary>
/// List of all powered ItemComponents
@@ -22,10 +69,9 @@ namespace Barotrauma.Items.Components
get { return poweredList; }
}
/// <summary>
/// Items that have already received the "probe signal" that's used to distribute power and load across the grid
/// </summary>
protected static HashSet<PowerTransfer> lastPowerProbeRecipients = new HashSet<PowerTransfer>();
public static readonly List<Connection> ChangedConnections = new List<Connection>();
public readonly static Dictionary<int, GridInfo> Grids = new Dictionary<int, GridInfo>();
/// <summary>
/// The amount of power currently consumed by the item. Negative values mean that the item is providing power to connected items
@@ -49,7 +95,9 @@ namespace Barotrauma.Items.Components
protected Connection powerIn, powerOut;
[Editable, Serialize(0.5f, true, description: "The minimum voltage required for the device to function. " +
protected virtual PowerPriority Priority { get { return PowerPriority.Default; } }
[Editable, Serialize(0.5f, IsPropertySaveable.Yes, description: "The minimum voltage required for the device to function. " +
"The voltage is calculated as power / powerconsumption, meaning that a device " +
"with a power consumption of 1000 kW would need at least 500 kW of power to work if the minimum voltage is set to 0.5.")]
public float MinVoltage
@@ -58,14 +106,14 @@ namespace Barotrauma.Items.Components
set { minVoltage = value; }
}
[Editable, Serialize(0.0f, true, description: "How much power the device draws (or attempts to draw) from the electrical grid when active.")]
[Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "How much power the device draws (or attempts to draw) from the electrical grid when active.")]
public float PowerConsumption
{
get { return powerConsumption; }
set { powerConsumption = value; }
}
[Serialize(false, true, description: "Is the device currently active. Inactive devices don't consume power.")]
[Serialize(false, IsPropertySaveable.Yes, description: "Is the device currently active. Inactive devices don't consume power.")]
public override bool IsActive
{
get { return base.IsActive; }
@@ -79,35 +127,63 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(0.0f, true, description: "The current power consumption of the device. Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
[Serialize(0.0f, IsPropertySaveable.Yes, description: "The current power consumption of the device. Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
public float CurrPowerConsumption
{
get {return currPowerConsumption; }
set { currPowerConsumption = value; }
}
[Serialize(0.0f, true, description: "The current voltage of the item (calculated as power consumption / available power). Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
[Serialize(0.0f, IsPropertySaveable.Yes, description: "The current voltage of the item (calculated as power consumption / available power). Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
public float Voltage
{
get { return voltage; }
set { voltage = Math.Max(0.0f, value); }
get
{
if (powerIn != null)
{
if (powerIn?.Grid != null) { return powerIn.Grid.Voltage; }
}
else if (powerOut != null)
{
if (powerOut?.Grid != null) { return powerOut.Grid.Voltage; }
}
return voltage;
}
set
{
if (powerIn != null)
{
if (powerIn.Grid != null)
{
powerIn.Grid.Voltage = Math.Max(0.0f, value);
}
}
else if (powerOut != null)
{
if (powerOut.Grid != null)
{
powerOut.Grid.Voltage = Math.Max(0.0f, value);
}
}
voltage = Math.Max(0.0f, value);
}
}
[Editable, Serialize(true, true, description: "Can the item be damaged by electomagnetic pulses.")]
[Editable, Serialize(true, IsPropertySaveable.Yes, description: "Can the item be damaged by electomagnetic pulses.")]
public bool VulnerableToEMP
{
get;
set;
}
public Powered(Item item, XElement element)
public Powered(Item item, ContentXElement element)
: base(item, element)
{
poweredList.Add(this);
InitProjectSpecific(element);
}
partial void InitProjectSpecific(XElement element);
partial void InitProjectSpecific(ContentXElement element);
protected void UpdateOnActiveEffects(float deltaTime)
{
@@ -115,19 +191,19 @@ namespace Barotrauma.Items.Components
{
//if the item consumes no power, ignore the voltage requirement and
//apply OnActive statuseffects as long as this component is active
if (powerConsumption <= 0.0f)
if (PowerConsumption <= 0.0f)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
}
return;
}
if (voltage > minVoltage)
if (Voltage > minVoltage)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
}
#if CLIENT
if (voltage > minVoltage)
if (Voltage > minVoltage)
{
if (!powerOnSoundPlayed && powerOnSound != null)
{
@@ -135,7 +211,7 @@ namespace Barotrauma.Items.Components
powerOnSoundPlayed = true;
}
}
else if (voltage < 0.1f)
else if (Voltage < 0.1f)
{
powerOnSoundPlayed = false;
}
@@ -144,7 +220,6 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
currPowerConsumption = powerConsumption;
UpdateOnActiveEffects(deltaTime);
}
@@ -163,6 +238,7 @@ namespace Barotrauma.Items.Components
else if (c.Name == "power_out")
{
powerOut = c;
powerOut.Priority = Priority;
}
else if (c.Name == "power")
{
@@ -182,6 +258,7 @@ namespace Barotrauma.Items.Components
#endif
}
powerOut = c;
powerOut.Priority = Priority;
}
else
{
@@ -199,104 +276,388 @@ namespace Barotrauma.Items.Components
}
}
public virtual void ReceivePowerProbeSignal(Connection connection, Item source, float power) { }
/// <summary>
/// Allocate electrical devices into their grids based on connections
/// </summary>
/// <param name="useCache">Use previous grids and change in connections</param>
public static void UpdateGrids(bool useCache = true)
{
//don't use cache if there are no existing grids
if (Grids.Count > 0 && useCache)
{
//delete all grids that were affected
foreach (Connection c in ChangedConnections)
{
if (c.Grid != null)
{
Grids.Remove(c.Grid.ID);
c.Grid = null;
}
}
foreach (Connection c in ChangedConnections)
{
//Make sure the connection grid hasn't been resolved by another connection update
//Ensure the connection has other connections
if (c.Grid == null && c.Recipients.Count > 0 && c.Item.Condition > 0.0f)
{
GridInfo grid = PropagateGrid(c);
Grids[grid.ID] = grid;
}
}
}
else
{
//Clear all grid IDs from connections
foreach (Powered powered in poweredList)
{
//Only check devices with connectors
if (powered.powerIn != null)
{
powered.powerIn.Grid = null;
}
if (powered.powerOut != null)
{
powered.powerOut.Grid = null;
}
}
Grids.Clear();
foreach (Powered powered in poweredList)
{
//Probe through all connections that don't have a gridID
if (powered.powerIn != null && powered.powerIn.Grid == null && powered.powerIn != powered.powerOut && powered.Item.Condition > 0.0f)
{
// Only create grids for networks with more than 1 device
if (powered.powerIn.Recipients.Count > 0)
{
GridInfo grid = PropagateGrid(powered.powerIn);
Grids[grid.ID] = grid;
}
}
if (powered.powerOut != null && powered.powerOut.Grid == null && powered.Item.Condition > 0.0f)
{
//Only create grids for networks with more than 1 device
if (powered.powerOut.Recipients.Count > 0)
{
GridInfo grid = PropagateGrid(powered.powerOut);
Grids[grid.ID] = grid;
}
}
}
}
//Clear changed connections after each update
ChangedConnections.Clear();
}
private static GridInfo PropagateGrid(Connection conn)
{
//Generate unique Key
int id = Rand.Int(int.MaxValue, Rand.RandSync.Unsynced);
while (Grids.ContainsKey(id))
{
id = Rand.Int(int.MaxValue, Rand.RandSync.Unsynced);
}
return PropagateGrid(conn, id);
}
private static GridInfo PropagateGrid(Connection conn, int gridID)
{
Stack<Connection> probeStack = new Stack<Connection>();
GridInfo grid = new GridInfo(gridID);
probeStack.Push(conn);
//Non recursive approach to traversing connection tree
while (probeStack.Count > 0)
{
Connection c = probeStack.Pop();
c.Grid = grid;
grid.AddConnection(c);
//Add on recipients
foreach (Connection otherC in c.Recipients)
{
//Only add valid connections
if (otherC.Grid != grid && (otherC.Grid == null || !Grids.ContainsKey(otherC.Grid.ID)) && ValidPowerConnection(c, otherC))
{
if (otherC.Item.Condition <= 0.0f)
{
continue;
}
otherC.Grid = grid; //Assigning ID early prevents unncessary adding to stack
probeStack.Push(otherC);
}
}
}
return grid;
}
/// <summary>
/// Update the power calculations of all devices and grids
/// Updates grids in the order of
/// ConnCurrConsumption - Get load of device/ flag it as an outputting connection
/// -- If outputting power --
/// MinMaxPower - Minimum and Maximum power output of the connection for devices to coordinate
/// ConnPowerOut - Final power output based on the sum of the MinMaxPower
/// -- Finally --
/// GridResolved - Indicate that a connection's grid has been finished being calculated
///
/// Power outputting devices are calculated in stages based on their priority
/// Reactors will output first, followed by relays then batteries.
///
/// </summary>
/// <param name="deltaTime"></param>
public static void UpdatePower(float deltaTime)
{
//Don't update the power if the round is ending
if (GameMain.GameSession != null && GameMain.GameSession.RoundEnding)
{
return;
}
//Only update the power at the given update interval
/*
//Not use currently as update interval of 1/60
if (updateTimer > 0.0f)
{
updateTimer -= deltaTime;
return;
}
updateTimer = UpdateInterval;
*/
//reset power first
foreach (Powered powered in poweredList)
#if CLIENT
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
#endif
//Ensure all grids are updated correctly and have the correct connections
UpdateGrids();
#if CLIENT
sw.Stop();
GameMain.PerformanceCounter.AddElapsedTicks("GridUpdate", sw.ElapsedTicks);
sw.Restart();
#endif
//Reset all grids
foreach (GridInfo grid in Grids.Values)
{
if (powered is PowerTransfer pt)
{
powered.CurrPowerConsumption = 0.0f;
pt.PowerLoad = 0.0f;
if (pt is RelayComponent relay)
{
relay.DisplayLoad = 0.0f;
}
}
//only reset voltage if the item has a power connector
//(other items, such as handheld devices, get power through other means and shouldn't be updated here)
if (powered.powerIn != null || powered.powerOut != null) { powered.voltage = 0.0f; }
//Wipe priority groups as connections can change to not be outputting -- Can be improved caching wise --
grid.PowerSourceGroups.Clear();
grid.Power = 0;
grid.Load = 0;
}
//go through all the devices that are consuming/providing power
//and send out a "probe signal" which the PowerTransfer components use to add up the grid power/load
//Determine if devices are adding a load or providing power, also resolve solo nodes
foreach (Powered powered in poweredList)
{
if (powered is PowerTransfer pt)
//Handle the device if it's got a power connection
if (powered.powerIn != null && powered.powerOut != powered.powerIn)
{
if (pt.ExtraLoad > 0.0f)
{
lastPowerProbeRecipients.Clear();
powered.powerIn?.SendPowerProbeSignal(powered.item, -pt.ExtraLoad);
//Get the new load for the connection
float currLoad;
if (powered.Item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering && repairable.TinkeringPowersDevices && !(powered is PowerContainer))
{
currLoad = 0.0f;
}
else
{
currLoad = powered.GetCurrentPowerConsumption(powered.powerIn);
}
continue;
}
else if (powered.currPowerConsumption > 0.0f)
{
//consuming power
lastPowerProbeRecipients.Clear();
powered.powerIn?.SendPowerProbeSignal(powered.item, -powered.currPowerConsumption);
}
}
foreach (Powered powered in poweredList)
{
if (powered is PowerTransfer) { continue; }
else if (powered.currPowerConsumption < 0.0f)
{
//providing power
lastPowerProbeRecipients.Clear();
powered.powerOut?.SendPowerProbeSignal(powered.item, -powered.currPowerConsumption);
}
if (powered is PowerContainer pc)
{
if (pc.CurrPowerOutput <= 0.0f || pc.item.Condition <= 0.0f) { continue; }
//providing power
lastPowerProbeRecipients.Clear();
powered.powerOut?.SendPowerProbeSignal(powered.item, pc.CurrPowerOutput);
}
}
//go through powered items and calculate their current voltage
foreach (Powered powered in poweredList)
{
if (powered is PowerTransfer pt1 || (pt1 = powered.Item.GetComponent<PowerTransfer>()) != null)
{
powered.voltage = -pt1.CurrPowerConsumption / Math.Max(pt1.PowerLoad, 1.0f);
continue;
}
if ((powered.powerConsumption <= 0.0f || (powered.Item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering && repairable.TinkeringPowersDevices)) && !(powered is PowerContainer))
{
powered.voltage = 1.0f;
continue;
}
if (powered.powerIn == null) { continue; }
foreach (Connection powerSource in powered.powerIn.Recipients)
{
if (!powerSource.IsPower || !powerSource.IsOutput) { continue; }
var pt = powerSource.Item.GetComponent<PowerTransfer>();
if (pt != null)
//If its a load update its grid load
if (currLoad >= 0)
{
float voltage = -pt.CurrPowerConsumption / Math.Max(pt.PowerLoad, 1.0f);
powered.voltage = Math.Max(powered.voltage, voltage);
continue;
powered.CurrPowerConsumption = currLoad;
if (powered.powerIn.Grid != null)
{
powered.powerIn.Grid.Load += currLoad;
}
}
var pc = powerSource.Item.GetComponent<PowerContainer>();
if (pc != null && pc.item.Condition > 0.0f)
else if (powered.powerIn.Grid != null)
{
float voltage = pc.CurrPowerOutput / Math.Max(powered.CurrPowerConsumption, 1.0f);
powered.voltage += voltage;
//If connected to a grid add as a source to be processed
powered.powerIn.Grid.AddSrc(powered.powerIn);
}
else
{
powered.CurrPowerConsumption = powered.GetConnectionPowerOut(powered.powerIn, 0, powered.MinMaxPowerOut(powered.powerIn, 0), 0);
powered.GridResolved(powered.powerIn);
}
}
//Handle the device power depending on if its powerout
if (powered.powerOut != null)
{
//Get the connection's load
float currLoad = powered.GetCurrentPowerConsumption(powered.powerOut);
//Update the device's output load to the correct variable
if (powered is PowerTransfer pt)
{
pt.PowerLoad = currLoad;
}
else if (powered is PowerContainer pc)
{
// PowerContainer handle its own output value
}
else
{
powered.CurrPowerConsumption = currLoad;
}
if (currLoad >= 0)
{
//Add to the grid load if possible
if (powered.powerOut.Grid != null)
{
powered.powerOut.Grid.Load += currLoad;
}
}
else if (powered.powerOut.Grid != null)
{
//Add connection as a source to be processed
powered.powerOut.Grid.AddSrc(powered.powerOut);
}
else
{
//Perform power calculations for the singular connection
float loadOut = powered.GetConnectionPowerOut(powered.powerOut, 0, powered.MinMaxPowerOut(powered.powerOut, 0), 0);
if (powered is PowerTransfer pt2)
{
pt2.PowerLoad = loadOut;
}
else if (powered is PowerContainer pc)
{
//PowerContainer handles its own output value
}
else
{
powered.CurrPowerConsumption = loadOut;
}
//Indicate grid is resolved as it was the only device
powered.GridResolved(powered.powerOut);
}
}
}
//Iterate through all grids to determine the power on the grid
foreach (GridInfo grid in Grids.Values)
{
//Iterate through the priority src groups lowest first
foreach (PowerSourceGroup scrGroup in grid.PowerSourceGroups.Values)
{
scrGroup.MinMaxPower = PowerRange.Zero;
//Iterate through all connections in the group to get their minmax power and sum them
foreach (Connection c in scrGroup.Connections)
{
Powered device = c.Item.GetComponent<Powered>();
scrGroup.MinMaxPower += device.MinMaxPowerOut(c, grid.Load);
}
//Iterate through all connections to get their final power out provided the min max information
float addedPower = 0;
foreach (Connection c in scrGroup.Connections)
{
Powered device = c.Item.GetComponent<Powered>();
addedPower += device.GetConnectionPowerOut(c, grid.Power, scrGroup.MinMaxPower, grid.Load);
}
//Add the power to the grid
grid.Power += addedPower;
}
//Calculate Grid voltage, limit between 0 - 1000
float newVoltage = MathHelper.Min(grid.Power / MathHelper.Max(grid.Load, 1E-10f), 1000);
if (float.IsNegative(newVoltage))
{
newVoltage = 0.0f;
}
grid.Voltage = newVoltage;
//Iterate through all connections on that grid and run their gridResolved function
foreach (Connection con in grid.Connections)
{
Powered device = con.Item.GetComponent<Powered>();
device.GridResolved(con);
}
}
#if CLIENT
sw.Stop();
GameMain.PerformanceCounter.AddElapsedTicks("PowerUpdate", sw.ElapsedTicks);
#endif
}
/// <summary>
/// Current power consumption of the device (or amount of generated power if negative)
/// </summary>
/// <param name="connection">Connection to calculate power consumption for.</param>
public virtual float GetCurrentPowerConsumption(Connection connection = null)
{
// If a handheld device there is no consumption
if (powerIn == null && powerOut == null)
{
return 0;
}
// Add extraload for PowerTransfer devices
if (this is PowerTransfer pt)
{
return PowerConsumption + pt.ExtraLoad;
}
else if (connection != this.powerIn || !IsActive)
{
//If not the power in connection or is inactive there is no draw
return 0;
}
//Otherwise return the max powerconsumption of the device
return PowerConsumption;
}
/// <summary>
/// Minimum and maximum power the connection can provide
/// </summary>
/// <param name="conn">Connection being queried about its power capabilities</param>
/// <param name="load">Load of the connected grid</param>
public virtual PowerRange MinMaxPowerOut(Connection conn, float load = 0)
{
return PowerRange.Zero;
}
/// <summary>
/// Finalize how much power the device will be outputting to the connection
/// </summary>
/// <param name="conn">Connection being queried</param>
/// <param name="power">Current grid power</param>
/// <param name="load">Current load on the grid</param>
/// <returns>Power pushed to the grid</returns>
public virtual float GetConnectionPowerOut(Connection conn, float power, PowerRange minMaxPower, float load)
{
return conn == powerOut ? MathHelper.Max(-CurrPowerConsumption, 0) : 0;
}
/// <summary>
/// Can be overridden to perform updates for the device after the connected grid has resolved its power calculations, i.e. storing voltage for later updates
/// </summary>
public virtual void GridResolved(Connection conn) { }
public static bool ValidPowerConnection(Connection conn1, Connection conn2)
{
return conn1.IsPower && conn2.IsPower && (conn1.Item.HasTag("junctionbox") || conn2.Item.HasTag("junctionbox") || conn1.IsOutput != conn2.IsOutput || (conn1.Item.HasTag("dock") && conn2.Item.HasTag("dock")));
}
/// <summary>
@@ -314,7 +675,6 @@ namespace Barotrauma.Items.Components
if (!recipient.IsPower || !recipient.IsOutput) { continue; }
var battery = recipient.Item?.GetComponent<PowerContainer>();
if (battery == null) { continue; }
float maxOutputPerFrame = battery.MaxOutPut / 60.0f;
float framesPerMinute = 3600.0f;
availablePower += Math.Min(battery.Charge * framesPerMinute, maxOutputPerFrame);
@@ -323,10 +683,119 @@ namespace Barotrauma.Items.Components
return availablePower;
}
/// <summary>
/// Efficient method to retrieve the batteries connected to the device
/// </summary>
/// <returns>All connected PowerContainers</returns>
protected List<PowerContainer> GetConnectedBatteries(bool outputOnly = true)
{
List<PowerContainer> batteries = new List<PowerContainer>();
GridInfo supplyingGrid = null;
//Determine supplying grid, prefer PowerIn connection
if (powerIn != null)
{
if (powerIn.Grid != null)
{
supplyingGrid = powerIn.Grid;
}
}
else if (powerOut != null)
{
if (powerOut.Grid != null)
{
supplyingGrid = powerOut.Grid;
}
}
if (supplyingGrid != null)
{
//Iterate through all connections to fine powerContainers
foreach (Connection c in supplyingGrid.Connections)
{
PowerContainer pc = c.Item.GetComponent<PowerContainer>();
if (pc != null && (!outputOnly || pc.powerOut == c))
{
batteries.Add(pc);
}
}
}
return batteries;
}
protected override void RemoveComponentSpecific()
{
//Flag power connections to be updated
if (item.Connections != null)
{
foreach (Connection c in item.Connections)
{
if (c.IsPower && c.Grid != null)
{
ChangedConnections.Add(c);
}
}
}
base.RemoveComponentSpecific();
poweredList.Remove(this);
}
}
partial class GridInfo
{
public readonly int ID;
public float Voltage = 0;
public float Load = 0;
public float Power = 0;
public readonly List<Connection> Connections = new List<Connection>();
public readonly SortedList<PowerPriority, PowerSourceGroup> PowerSourceGroups = new SortedList<PowerPriority, PowerSourceGroup>();
public GridInfo(int id)
{
ID = id;
}
public void RemoveConnection(Connection c)
{
Connections.Remove(c);
//Remove the grid if it has no devices
if (Connections.Count == 0 && Powered.Grids.ContainsKey(ID))
{
Powered.Grids.Remove(ID);
}
}
public void AddConnection(Connection c)
{
Connections.Add(c);
}
public void AddSrc(Connection c)
{
if (PowerSourceGroups.ContainsKey(c.Priority))
{
PowerSourceGroups[c.Priority].Connections.Add(c);
}
else
{
PowerSourceGroup group = new PowerSourceGroup();
group.Connections.Add(c);
PowerSourceGroups[c.Priority] = group;
}
}
}
partial class PowerSourceGroup
{
public PowerRange MinMaxPower;
public readonly List<Connection> Connections = new List<Connection>();
public PowerSourceGroup()
{
}
}
}