Release 1.9.7.0 - Summer Update 2025

This commit is contained in:
Regalis11
2025-06-17 16:38:11 +03:00
parent 22227f13e5
commit ea5a2bc693
297 changed files with 7344 additions and 2421 deletions
@@ -0,0 +1,230 @@
#nullable enable
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
internal partial class PowerDistributor : PowerTransfer
{
private const int MaxNameLength = 32;
private const int SupplyRatioSteps = 20;
private const float SupplyRatioStep = 1f / SupplyRatioSteps;
private partial class PowerGroup
{
private readonly PowerDistributor distributor;
public readonly Connection PowerOut;
public readonly Connection? RatioInput, RatioOutput;
private string name;
public string Name
{
get => name;
set
{
name = value;
DisplayName = TextManager.Get(name).Fallback(name);
#if CLIENT
UpdateNameBox();
#endif
}
}
public LocalizedString? DisplayName { get; private set; }
private float supplyRatio = 1f;
public float SupplyRatio
{
get => supplyRatio;
set
{
if (!MathUtils.IsValid(value)) { return; }
supplyRatio = MathUtils.RoundTowardsClosest(MathHelper.Clamp(value, 0f, 1f), SupplyRatioStep);
#if CLIENT
UpdateSlider();
#endif
}
}
public float DisplayRatio
{
get => MathUtils.RoundToInt(supplyRatio * 100);
set => SupplyRatio = value / 100f;
}
public float Load;
public float ModifiedLoad => Load * SupplyRatio;
public PowerGroup(PowerDistributor distributor, Connection power, XElement? element = null, Connection? ratioInput = null, Connection? ratioOutput = null)
{
this.distributor = distributor;
PowerOut = power;
RatioInput = ratioInput;
RatioOutput = ratioOutput;
distributor.powerGroups.Add(this);
name = TextManager.GetWithVariable("groupx", "[num]", distributor.powerGroups.Count.ToString()).Value;
SupplyRatio = 1f;
if (element != null)
{
name = element.GetAttributeString("name", name);
SupplyRatio = element.GetAttributeFloat("ratio", SupplyRatio);
}
#if CLIENT
CreateGUI();
#endif
}
#region Signals
public void ReceiveRatioSignal(Signal signal)
{
if (!float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float receivedSignal) || !MathUtils.IsValid(receivedSignal)) { return; }
DisplayRatio = receivedSignal;
}
public void SendRatioSignal() => distributor.item.SendSignal(new Signal(DisplayRatio.ToString()), RatioOutput);
#endregion
}
private readonly List<PowerGroup> powerGroups = new List<PowerGroup>();
protected override PowerPriority Priority => PowerPriority.Relay;
public PowerDistributor(Item item, ContentXElement element) : base(item, element) { }
public override void OnItemLoaded()
{
base.OnItemLoaded();
IEnumerable<Connection> ratioInputs = Item.Connections.Where(static conn => !conn.IsOutput && conn.Name.StartsWith("set_supply_ratio"));
IEnumerable<Connection> ratioOutputs = Item.Connections.Where(static conn => conn.IsOutput && conn.Name.StartsWith("supply_ratio_out"));
for (int i = 0; i < powerOuts.Count; i++)
{
new PowerGroup(this, powerOuts[i], cachedGroupData.ElementAtOrDefault(i), ratioInputs.ElementAtOrDefault(i), ratioOutputs.ElementAtOrDefault(i));
}
cachedGroupData.Clear();
}
public override void Clone(ItemComponent original)
{
if (original is not PowerDistributor originalPowerDistributor) { return; }
for (int i = 0; i < powerOuts.Count; i++)
{
powerGroups[i].SupplyRatio = originalPowerDistributor.powerGroups[i].SupplyRatio;
powerGroups[i].Name = originalPowerDistributor.powerGroups[i].Name;
}
}
#region Signals
protected override void SendSignals()
{
item.SendSignal(MathUtils.RoundToInt(powerIn.Grid?.Power ?? 0f).ToString(), "power_value_out");
item.SendSignal(MathUtils.RoundToInt(GetCurrentPowerConsumption(powerIn)).ToString(), "load_value_out");
powerGroups.ForEach(static group => group.SendRatioSignal());
}
public override void ReceiveSignal(Signal signal, Connection connection)
{
if (item.Condition <= 0f || connection.IsPower) { return; }
if (connection.IsOutput) { return; }
powerGroups.FirstOrDefault(group => group.RatioInput == connection)?.ReceiveRatioSignal(signal);
}
#endregion
#region Power Calculation
private bool IsShortCircuited(Connection conn) => powerIn.Grid == conn.Grid;
public override float GetCurrentPowerConsumption(Connection? connection = null)
{
if (connection != powerIn) { return -1f; }
if (isBroken) { return 0f; }
return powerGroups.Sum(group => IsShortCircuited(group.PowerOut) ? 0f : group.ModifiedLoad) + ExtraLoad;
}
private float CalculatePowerOut(PowerGroup group)
{
if (isBroken || powerIn.Grid == null || IsShortCircuited(group.PowerOut)) { return 0f; }
return Math.Max(group.ModifiedLoad * Voltage, 0f);
}
public override float GetConnectionPowerOut(Connection connection, float power, PowerRange minMaxPower, float load)
{
if (connection == powerIn) { return 0f; }
PowerGroup group = powerGroups.First(group => group.PowerOut == connection);
group.Load = load;
return CalculatePowerOut(group);
}
#endregion
#region Serialization
private readonly List<XElement> cachedGroupData = new List<XElement>();
public override XElement Save(XElement parentElement)
{
XElement componentElement = base.Save(parentElement);
foreach (PowerGroup powerGroup in powerGroups)
{
componentElement.Add(new XElement("PowerGroup",
new XAttribute("name", powerGroup.Name),
new XAttribute("ratio", powerGroup.SupplyRatio)));
}
return componentElement;
}
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
{
base.Load(componentElement, usePrefabValues, idRemap, isItemSwap);
if (usePrefabValues) { return; }
foreach (XElement element in componentElement.Elements())
{
cachedGroupData.Add(element);
}
}
#endregion
#region Networking
private enum EventType { NameChange, RatioChange }
private void SharedEventWrite(IWriteMessage msg, NetEntityEvent.IData? extraData = null)
{
EventData data = ExtractEventData<EventData>(extraData);
msg.WriteRangedInteger((int)data.EventType, 0, 1);
msg.WriteRangedInteger(powerGroups.IndexOf(data.PowerGroup), 0, powerGroups.Count - 1);
switch (data.EventType)
{
case EventType.NameChange:
msg.WriteString(data.PowerGroup.Name);
break;
case EventType.RatioChange:
msg.WriteRangedInteger(MathUtils.RoundToInt(data.PowerGroup.SupplyRatio / SupplyRatioStep), 0, SupplyRatioSteps);
break;
}
}
private void SharedEventRead(IReadMessage msg, out EventType eventType, out PowerGroup powerGroup, out string newName, out float newRatio)
{
eventType = (EventType)msg.ReadRangedInteger(0, 1);
powerGroup = powerGroups[msg.ReadRangedInteger(0, powerGroups.Count - 1)];
newName = eventType == EventType.NameChange ? string.Concat(msg.ReadString().Take(MaxNameLength)) : powerGroup.Name;
newRatio = eventType == EventType.RatioChange ? msg.ReadRangedInteger(0, SupplyRatioSteps) * SupplyRatioStep : powerGroup.SupplyRatio;
}
private readonly record struct EventData(PowerGroup PowerGroup, EventType EventType) : IEventData;
#endregion
}
}
@@ -177,18 +177,7 @@ namespace Barotrauma.Items.Components
{
RefreshConnections();
if (Timing.TotalTime > extraLoadSetTime + 1.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);
}
}
UpdateExtraLoad(deltaTime);
if (!CanTransfer) { return; }
@@ -200,6 +189,28 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnActive, deltaTime);
SendSignals();
UpdateOvervoltage(deltaTime);
}
protected virtual void UpdateExtraLoad(float deltaTime)
{
if (Timing.TotalTime <= extraLoadSetTime + 1.0) { return; }
//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);
}
}
protected virtual void SendSignals()
{
float powerReadingOut = 0;
float loadReadingOut = ExtraLoad;
if (powerLoad < 0)
@@ -226,7 +237,10 @@ namespace Barotrauma.Items.Components
}
item.SendSignal(powerSignal, "power_value_out");
item.SendSignal(loadSignal, "load_value_out");
}
protected virtual void UpdateOvervoltage(float deltaTime)
{
//if the item can't be fixed, don't allow it to break
if (!item.Repairables.Any() || !CanBeOverloaded) { return; }
@@ -234,46 +248,45 @@ namespace Barotrauma.Items.Components
Overload = Voltage > maxOverVoltage && GameMain.GameSession is not { RoundDuration: < 5 };
if (Overload && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
if (!Overload || GameMain.NetworkMember is { IsClient: true }) { return; }
if (overloadCooldownTimer > 0.0f)
{
if (overloadCooldownTimer > 0.0f)
{
overloadCooldownTimer -= deltaTime;
return;
}
overloadCooldownTimer -= deltaTime;
return;
}
//damage the item if voltage is too high (except if running as a client)
float prevCondition = item.Condition;
//some randomness to prevent all junction boxes from breaking at the same time
if (Rand.Range(0.0f, 1.0f) < 0.01f)
{
//damaged boxes are more sensitive to overvoltage (also preventing all boxes from breaking at the same time)
float conditionFactor = MathHelper.Lerp(5.0f, 1.0f, item.Condition / item.MaxCondition);
item.Condition -= deltaTime * Rand.Range(10.0f, 500.0f) * conditionFactor;
}
if (item.Condition <= 0.0f && prevCondition > 0.0f)
{
overloadCooldownTimer = OverloadCooldown;
//damage the item if voltage is too high (except if running as a client)
float prevCondition = item.Condition;
//some randomness to prevent all junction boxes from breaking at the same time
if (Rand.Range(0.0f, 1.0f) < 0.01f)
{
//damaged boxes are more sensitive to overvoltage (also preventing all boxes from breaking at the same time)
float conditionFactor = MathHelper.Lerp(5.0f, 1.0f, item.Condition / item.MaxCondition);
item.Condition -= deltaTime * Rand.Range(10.0f, 500.0f) * conditionFactor;
}
if (item.Condition > 0.0f || prevCondition <= 0.0f) { return; }
overloadCooldownTimer = OverloadCooldown;
#if CLIENT
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
Vector2 baseVel = Rand.Vector(300.0f);
for (int i = 0; i < 10; i++)
{
var particle = GameMain.ParticleManager.CreateParticle("spark", item.WorldPosition,
baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
if (particle != null) particle.Size *= Rand.Range(0.5f, 1.0f);
}
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
Vector2 baseVel = Rand.Vector(300.0f);
for (int i = 0; i < 10; i++)
{
var particle = GameMain.ParticleManager.CreateParticle("spark", item.WorldPosition,
baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
if (particle != null) particle.Size *= Rand.Range(0.5f, 1.0f);
}
#endif
float currentIntensity = GameMain.GameSession?.EventManager != null ?
GameMain.GameSession.EventManager.CurrentIntensity : 0.5f;
float currentIntensity = GameMain.GameSession?.EventManager != null ?
GameMain.GameSession.EventManager.CurrentIntensity : 0.5f;
//higher probability for fires if the current intensity is low
if (FireProbability > 0.0f &&
Rand.Range(0.0f, 1.0f) < MathHelper.Lerp(FireProbability, FireProbability * 0.1f, currentIntensity))
{
new FireSource(item.WorldPosition);
}
}
//higher probability for fires if the current intensity is low
if (FireProbability > 0.0f &&
Rand.Range(0.0f, 1.0f) < MathHelper.Lerp(FireProbability, FireProbability * 0.1f, currentIntensity))
{
new FireSource(item.WorldPosition);
}
}
@@ -424,7 +437,7 @@ namespace Barotrauma.Items.Components
}
}
if (this is not RelayComponent)
if (this is not RelayComponent and not PowerDistributor)
{
if (PowerConnections.Any(p => !p.IsOutput) && PowerConnections.Any(p => p.IsOutput))
{
@@ -452,7 +465,7 @@ namespace Barotrauma.Items.Components
{
//other junction boxes don't need to receive the signal in the pass-through signal connections
//because we relay it straight to the connected items without going through the whole chain of junction boxes
if (ic is PowerTransfer && ic is not RelayComponent) { continue; }
if (ic is PowerTransfer and not RelayComponent and not PowerDistributor) { continue; }
ic.ReceiveSignal(signal, recipient);
}
@@ -2,6 +2,7 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
#if CLIENT
using Barotrauma.Sounds;
#endif
@@ -93,7 +94,22 @@ namespace Barotrauma.Items.Components
/// </summary>
protected float powerConsumption;
protected Connection powerIn, powerOut;
protected Connection powerIn;
protected List<Connection> powerOuts = new List<Connection>();
/// <summary>
/// Throws an error if there is more than one power out connection.<br/>
/// Use <see cref="powerOuts"/> if a component should handle multiple outputs.
/// </summary>
protected Connection powerOut
{
get
{
if (powerOuts.Count > 1) { DebugConsole.ThrowErrorOnce($"{item.ID}.multiplePowerOut", $"Item {item.Name} ({item.Prefab.Identifier}) has multiple power outputs, but only supports one!"); }
return powerOuts.FirstOrDefault();
}
}
protected bool powerInIsPowerOut => powerOuts.Contains(powerIn);
/// <summary>
/// Maximum voltage factor when the device is being overvolted. I.e. how many times more effectively the device can function when it's being overvolted
@@ -153,9 +169,10 @@ namespace Barotrauma.Items.Components
{
if (powerIn?.Grid != null) { return powerIn.Grid.Voltage; }
}
else if (powerOut != null)
else if (powerOuts.Any())
{
if (powerOut?.Grid != null) { return powerOut.Grid.Voltage; }
IEnumerable<Connection> gridConnections = powerOuts.Where(static conn => conn.Grid != null);
if (gridConnections.Any()) { return gridConnections.Average(static conn => conn.Grid.Voltage); }
}
if (this is PowerTransfer && item.Condition <= 0.0f)
@@ -245,18 +262,19 @@ namespace Barotrauma.Items.Components
{
powerIn = c;
}
else if (c.Name == "power_out")
{
powerOut = c;
// Connection takes the lowest priority
if (Priority > powerOut.Priority)
{
powerOut.Priority = Priority;
}
}
else if (c.Name == "power")
{
powerIn = powerOut = c;
powerIn = c;
powerOuts.Add(c);
}
else if (c.IsOutput)
{
powerOuts.Add(c);
// Connection takes the lowest priority
if (Priority > c.Priority)
{
c.Priority = Priority;
}
}
}
else
@@ -271,11 +289,11 @@ namespace Barotrauma.Items.Components
DebugConsole.NewMessage($"Item \"{item.Name}\" has a power output connection called power_in. If the item is supposed to receive power through the connection, change it to an input connection.", Color.Orange);
#endif
}
powerOut = c;
powerOuts.Add(c);
// Connection takes the lowest priority
if (Priority > powerOut.Priority)
if (Priority > c.Priority)
{
powerOut.Priority = Priority;
c.Priority = Priority;
}
}
else
@@ -335,9 +353,9 @@ namespace Barotrauma.Items.Components
{
powered.powerIn.Grid = null;
}
if (powered.powerOut != null)
foreach (Connection powerOut in powered.powerOuts)
{
powered.powerOut.Grid = null;
powerOut.Grid = null;
}
}
@@ -345,8 +363,10 @@ namespace Barotrauma.Items.Components
foreach (Powered powered in poweredList)
{
if (powered.Item.Condition <= 0f) { continue; }
//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)
if (powered.powerIn != null && powered.powerIn.Grid == null && !powered.powerInIsPowerOut)
{
// Only create grids for networks with more than 1 device
if (powered.powerIn.Recipients.Count > 0)
@@ -356,13 +376,16 @@ namespace Barotrauma.Items.Components
}
}
if (powered.powerOut != null && powered.powerOut.Grid == null && powered.Item.Condition > 0.0f)
foreach (Connection powerOut in powered.powerOuts)
{
//Only create grids for networks with more than 1 device
if (powered.powerOut.Recipients.Count > 0)
if (powerOut != null && powerOut.Grid == null)
{
GridInfo grid = PropagateGrid(powered.powerOut);
Grids[grid.ID] = grid;
//Only create grids for networks with more than 1 device
if (powerOut.Recipients.Count > 0)
{
GridInfo grid = PropagateGrid(powerOut);
Grids[grid.ID] = grid;
}
}
}
}
@@ -479,7 +502,7 @@ namespace Barotrauma.Items.Components
powered.Voltage -= deltaTime;
//Handle the device if it's got a power connection
if (powered.powerIn != null && powered.powerOut != powered.powerIn)
if (powered.powerIn != null && !powered.powerInIsPowerOut)
{
//Get the new load for the connection
float currLoad = powered.GetCurrentPowerConsumption(powered.powerIn);
@@ -507,10 +530,10 @@ namespace Barotrauma.Items.Components
}
//Handle the device power depending on if its powerout
if (powered.powerOut != null)
foreach (Connection powerOut in powered.powerOuts)
{
//Get the connection's load
float currLoad = powered.GetCurrentPowerConsumption(powered.powerOut);
float currLoad = powered.GetCurrentPowerConsumption(powerOut);
//Update the device's output load to the correct variable
if (powered is PowerTransfer pt)
@@ -529,20 +552,20 @@ namespace Barotrauma.Items.Components
if (currLoad >= 0)
{
//Add to the grid load if possible
if (powered.powerOut.Grid != null)
if (powerOut.Grid != null)
{
powered.powerOut.Grid.Load += currLoad;
powerOut.Grid.Load += currLoad;
}
}
else if (powered.powerOut.Grid != null)
else if (powerOut.Grid != null)
{
//Add connection as a source to be processed
powered.powerOut.Grid.AddSrc(powered.powerOut);
powerOut.Grid.AddSrc(powerOut);
}
else
{
//Perform power calculations for the singular connection
float loadOut = -powered.GetConnectionPowerOut(powered.powerOut, 0, powered.MinMaxPowerOut(powered.powerOut, 0), 0);
float loadOut = -powered.GetConnectionPowerOut(powerOut, 0, powered.MinMaxPowerOut(powerOut, 0), 0);
if (powered is PowerTransfer pt2)
{
pt2.PowerLoad = loadOut;
@@ -557,7 +580,7 @@ namespace Barotrauma.Items.Components
}
//Indicate grid is resolved as it was the only device
powered.GridResolved(powered.powerOut);
powered.GridResolved(powerOut);
}
}
}
@@ -625,7 +648,7 @@ namespace Barotrauma.Items.Components
public virtual float GetCurrentPowerConsumption(Connection connection = null)
{
// If a handheld device there is no consumption
if (powerIn == null && powerOut == null)
if (powerIn == null && powerOuts.None())
{
return 0;
}
@@ -664,7 +687,7 @@ namespace Barotrauma.Items.Components
/// <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;
return powerOuts.Contains(conn) ? MathHelper.Max(-CurrPowerConsumption, 0) : 0;
}
/// <summary>