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 } }