diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OxygenGenerator.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OxygenGenerator.cs
new file mode 100644
index 0000000000..61ff96935d
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/OxygenGenerator.cs
@@ -0,0 +1,117 @@
+using Barotrauma.Networking;
+using Microsoft.Xna.Framework;
+
+namespace Barotrauma.Items.Components
+{
+ internal partial class OxygenGenerator : IServerSerializable, IClientSerializable
+ {
+ private GUITickBox powerIndicator, autoControlIndicator;
+ private GUIScrollBar rateSlider;
+
+ [Serialize(0.5f, IsPropertySaveable.Yes)]
+ public float RateWarningIndicatorLow { get; set; }
+
+ [Serialize(0.25f, IsPropertySaveable.Yes)]
+ public float RateWarningIndicatorExtremelyLow { get; set; }
+
+ protected override void CreateGUI()
+ {
+ if (GuiFrame == null)
+ {
+ DebugConsole.AddWarning($"OxygenGenerator component of {Item.Name} ({Item.Prefab.Identifier}) has no GUIFrame defined.", Item.ContentPackage);
+ return;
+ }
+ GUILayoutGroup paddedFrame = new(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset })
+ {
+ Stretch = true,
+ RelativeSpacing = 0.1f
+ };
+
+ int indicatorHeight = MathUtils.RoundToInt(GUIStyle.SubHeadingFont.LineHeight * 2);
+ GUILayoutGroup indicatorArea = new(new RectTransform(Vector2.One, paddedFrame.RectTransform), isHorizontal: true, Anchor.CenterLeft) { Stretch = true };
+ powerIndicator = new GUITickBox(new RectTransform(Vector2.UnitX, indicatorArea.RectTransform, minSize: (0, indicatorHeight)), TextManager.Get("EnginePowered"), GUIStyle.SubHeadingFont, style: "IndicatorLightGreen")
+ {
+ CanBeFocused = false,
+ OnAddedToGUIUpdateList = comp => comp.Selected = HasPower && IsActive,
+ TextBlock = { Wrap = true }
+ };
+ powerIndicator.TextBlock.OverrideTextColor(GUIStyle.TextColorNormal);
+ autoControlIndicator = new GUITickBox(new RectTransform(Vector2.UnitX, indicatorArea.RectTransform, minSize: (0, indicatorHeight)), TextManager.Get("PumpAutoControl", "ReactorAutoControl"), GUIStyle.SubHeadingFont, style: "IndicatorLightYellow")
+ {
+ Enabled = false,
+ OnAddedToGUIUpdateList = comp => comp.Selected = controlLockTimer > 0f,
+ ToolTip = TextManager.Get("AutoControlTip"),
+ TextBlock = { Wrap = true, TextAlignment = Alignment.CenterRight }
+ };
+ autoControlIndicator.TextBlock.OverrideTextColor(GUIStyle.TextColorNormal);
+ autoControlIndicator.TextBlock.SetAsFirstChild();
+ GUITextBlock.AutoScaleAndNormalize(powerIndicator.TextBlock, autoControlIndicator.TextBlock);
+
+ GUITextBlock rateName = new(new RectTransform(Vector2.UnitX, paddedFrame.RectTransform), TextManager.Get("OxygenGenerationRate"), GUIStyle.TextColorNormal, GUIStyle.SubHeadingFont, Alignment.CenterLeft);
+ GUITextBlock rateText = new(new RectTransform(Vector2.One, rateName.RectTransform), "", GUIStyle.TextColorNormal, GUIStyle.Font, Alignment.CenterRight)
+ {
+ TextGetter = () => $"{MathUtils.RoundToInt(generatedAmount * generationRatio)}/s ({MathUtils.RoundToInt(generationRatio * 100f)}%)"
+ };
+ if (rateText.TextSize.X > rateText.Rect.Width) { rateText.Font = GUIStyle.SmallFont; }
+
+ GUIFrame rateSliderContainer = new(new RectTransform(Vector2.UnitX, paddedFrame.RectTransform, minSize: (0, 50)));
+ new GUICustomComponent(new RectTransform(new Vector2(0.95f, 0.9f), rateSliderContainer.RectTransform, Anchor.Center), (sb, comp) =>
+ {
+ if (RateWarningIndicatorLow > 0f)
+ {
+ GUI.DrawRectangle(sb, comp.Rect.Location.ToVector2(), ( comp.Rect.Width * RateWarningIndicatorLow, comp.Rect.Height), GUIStyle.Orange, isFilled: true);
+ }
+ if (RateWarningIndicatorExtremelyLow > 0f)
+ {
+ GUI.DrawRectangle(sb, comp.Rect.Location.ToVector2(), (comp.Rect.Width * RateWarningIndicatorExtremelyLow, comp.Rect.Height), GUIStyle.Red, isFilled: true);
+ }
+ });
+ rateSlider = new GUIScrollBar(new RectTransform(Vector2.One, rateSliderContainer.RectTransform, Anchor.Center), barSize: 0.1f, isHorizontal: true, style: "DeviceSliderSeeThrough")
+ {
+ Step = GenerationRatioStep,
+ OnMoved = (_, newRatio) =>
+ {
+ GenerationRatio = newRatio;
+ if (GameMain.Client != null)
+ {
+ item.CreateClientEvent(this);
+ correctionTimer = CorrectionDelay;
+ }
+ return true;
+ },
+ OnAddedToGUIUpdateList = comp => comp.Enabled = controlLockTimer <= 0f
+ };
+ rateSlider.Bar.RectTransform.MaxSize = new Point(rateSlider.Bar.Rect.Height);
+ }
+
+ partial void InitProjSpecific() => CreateGUI();
+
+ public override void OnItemLoaded()
+ {
+ base.OnItemLoaded();
+ UpdateSlider();
+ }
+
+ #region Networking
+ public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData) => WriteGenerationRatio(msg);
+ public void ClientEventRead(IReadMessage msg, float sendingTime)
+ {
+ if (correctionTimer > 0f)
+ {
+ StartDelayedCorrection(msg.ExtractBits(4), sendingTime);
+ return;
+ }
+
+ GenerationRatio = ReadGenerationRatio(msg);
+ }
+ #endregion
+
+ private void UpdateSlider()
+ {
+ if (rateSlider != null)
+ {
+ rateSlider.BarScroll = generationRatio;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/OxygenGenerator.cs b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/OxygenGenerator.cs
new file mode 100644
index 0000000000..3dadc70507
--- /dev/null
+++ b/Barotrauma/BarotraumaServer/ServerSource/Items/Components/Machines/OxygenGenerator.cs
@@ -0,0 +1,23 @@
+using Barotrauma.Networking;
+
+namespace Barotrauma.Items.Components
+{
+ internal partial class OxygenGenerator : IServerSerializable, IClientSerializable
+ {
+ #region Networking
+ public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null) => WriteGenerationRatio(msg);
+ public void ServerEventRead(IReadMessage msg, Client c)
+ {
+ float newGenerationRatio = ReadGenerationRatio(msg);
+
+ if (item.CanClientAccess(c))
+ {
+ GenerationRatio = newGenerationRatio;
+ GameServer.Log($"{GameServer.CharacterLogName(c.Character)} set the oxygen generation amount of {item.Name} to {MathUtils.RoundToInt(generationRatio * 100f)}%", ServerLog.MessageType.ItemInteraction);
+ }
+
+ item.CreateServerEvent(this);
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/LocalMods/oxygen-changes/Locale/English.xml b/Barotrauma/BarotraumaShared/LocalMods/oxygen-changes/Locale/English.xml
new file mode 100644
index 0000000000..a7f75d4f0a
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/LocalMods/oxygen-changes/Locale/English.xml
@@ -0,0 +1,5 @@
+
+ Oxygen Generation Rate
+ set_oxygen_rate
+ oxygen_rate_out
+
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/LocalMods/oxygen-changes/OxygenGenerators.xml b/Barotrauma/BarotraumaShared/LocalMods/oxygen-changes/OxygenGenerators.xml
new file mode 100644
index 0000000000..c3f940c95f
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/LocalMods/oxygen-changes/OxygenGenerators.xml
@@ -0,0 +1,263 @@
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/LocalMods/oxygen-changes/Upgrades.xml b/Barotrauma/BarotraumaShared/LocalMods/oxygen-changes/Upgrades.xml
new file mode 100644
index 0000000000..f3bca911fc
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/LocalMods/oxygen-changes/Upgrades.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/LocalMods/oxygen-changes/filelist.xml b/Barotrauma/BarotraumaShared/LocalMods/oxygen-changes/filelist.xml
new file mode 100644
index 0000000000..5ad3c8a691
--- /dev/null
+++ b/Barotrauma/BarotraumaShared/LocalMods/oxygen-changes/filelist.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs
index 811deb8412..9c950efcd6 100644
--- a/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs
+++ b/Barotrauma/BarotraumaShared/SharedSource/Items/Components/Machines/OxygenGenerator.cs
@@ -1,13 +1,19 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
+using Barotrauma.Networking;
namespace Barotrauma.Items.Components
{
- class OxygenGenerator : Powered
+ internal partial class OxygenGenerator : Powered
{
+ private const int GenerationRatioSteps = 10;
+ private const float GenerationRatioStep = 1f / GenerationRatioSteps;
+
private float generatedAmount;
+ private float generationRatio;
//key = vent, float = total volume of the hull the vent is in and the hulls connected to it
private List<(Vent vent, float hullVolume)> ventList;
@@ -16,7 +22,10 @@ class OxygenGenerator : Powered
private float ventUpdateTimer;
const float VentUpdateInterval = 5.0f;
+
+ private float controlLockTimer;
+ [Serialize(0f, IsPropertySaveable.No, "The current adjusted oxygen output of the generator. Setting this value in XML has no effect.")]
public float CurrFlow
{
get;
@@ -29,19 +38,40 @@ public float GeneratedAmount
get { return generatedAmount; }
set { generatedAmount = MathHelper.Clamp(value, -10000.0f, 10000.0f); }
}
+
+ [Editable(0f, 1f), Serialize(1f, IsPropertySaveable.Yes, "The ratio of the max generation capacity this machine is currently outputting.", alwaysUseInstanceValues: true)]
+ public float GenerationRatio
+ {
+ get => generationRatio;
+ set
+ {
+ if (!MathUtils.IsValid(value)) { return; }
+ generationRatio = MathUtils.RoundTowardsClosest(MathHelper.Clamp(value, 0f, 1f), GenerationRatioStep);
+#if CLIENT
+ UpdateSlider();
+#endif
+ }
+ }
public OxygenGenerator(Item item, ContentXElement element)
: base(item, element)
{
//randomize update timer so all oxygen generators don't update at the same time
ventUpdateTimer = Rand.Range(0.0f, VentUpdateInterval);
+ InitProjSpecific();
IsActive = true;
}
+
+ partial void InitProjSpecific();
+
+ public override bool Pick(Character picker) => picker != null;
public override void Update(float deltaTime, Camera cam)
{
UpdateOnActiveEffects(deltaTime);
+ controlLockTimer -= deltaTime;
+
CurrFlow = 0.0f;
if (item.CurrentHull == null) { return; }
@@ -51,7 +81,7 @@ public override void Update(float deltaTime, Camera cam)
return;
}
- CurrFlow = Math.Min(PowerConsumption > 0 ? Voltage : 1.0f, MaxOverVoltageFactor) * generatedAmount * 100.0f;
+ CurrFlow = Math.Min(PowerConsumption > 0 ? Voltage : 1.0f, MaxOverVoltageFactor) * generatedAmount * generationRatio * 100f;
float conditionMult = item.Condition / item.MaxCondition;
//100% condition = 100% oxygen
//50% condition = 25% oxygen
@@ -59,6 +89,8 @@ public override void Update(float deltaTime, Camera cam)
CurrFlow *= conditionMult * conditionMult;
UpdateVents(CurrFlow, deltaTime);
+
+ item.SendSignal(MathUtils.RoundToInt(generationRatio * 100).ToString(), "rate_out");
}
///
@@ -71,13 +103,28 @@ public override float GetCurrentPowerConsumption(Connection connection = null)
return 0;
}
- float consumption = powerConsumption;
+ float consumption = powerConsumption * generationRatio;
//consume more power when in a bad condition
item.GetComponent()?.AdjustPowerConsumption(ref consumption);
return consumption;
}
+ public override void ReceiveSignal(Signal signal, Connection connection)
+ {
+ if (connection.IsPower) { return; }
+ switch (connection.Name)
+ {
+ case "set_rate":
+ if (float.TryParse(signal.value, NumberStyles.Any, CultureInfo.InvariantCulture, out float newRate) && MathUtils.IsValid(newRate))
+ {
+ controlLockTimer = 0.1f;
+ GenerationRatio = MathHelper.Clamp(newRate / 100f, 0f, 1f);
+ }
+ break;
+ }
+ }
+
public override void UpdateBroken(float deltaTime, Camera cam)
{
base.UpdateBroken(deltaTime, cam);
@@ -146,5 +193,10 @@ public float GetVentOxygenFlow(Vent targetVent)
}
return 0.0f;
}
+
+ #region Networking
+ private static float ReadGenerationRatio(IReadMessage msg) => msg.ReadRangedInteger(0, GenerationRatioSteps) * GenerationRatioStep;
+ private void WriteGenerationRatio(IWriteMessage msg) => msg.WriteRangedInteger(MathUtils.RoundToInt(generationRatio / GenerationRatioStep), 0, GenerationRatioSteps);
+ #endregion
}
}