using Barotrauma.Networking; using Lidgren.Network; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; namespace Barotrauma.Items.Components { partial class Reactor : Powered, IServerSerializable, IClientSerializable { private GUIScrollBar autoTempSlider; private GUIScrollBar onOffSwitch; private const int GraphSize = 25; private float graphTimer; private int updateGraphInterval = 500; private Sprite fissionRateMeter, turbineOutputMeter; private Sprite meterPointer; private Sprite sectorSprite; private Sprite tempMeterFrame, tempMeterBar; private Sprite tempRangeIndicator; private Sprite graphLine; private GUIScrollBar fissionRateScrollBar; private GUIScrollBar turbineOutputScrollBar; private float[] outputGraph = new float[GraphSize]; private float[] loadGraph = new float[GraphSize]; private GUITickBox criticalHeatWarning; private GUITickBox lowTemperatureWarning; private GUITickBox criticalOutputWarning; private GUIFrame inventoryContainer; private GUIComponent leftHUDColumn; private Dictionary warningButtons = new Dictionary(); private static string[] warningTexts = new string[] { "ReactorWarningLowTemp","ReactorWarningOverheating", "ReactorWarningLowOutput", "ReactorWarningHighOutput", "ReactorWarningLowFuel", "ReactorWarningFuelOut", "ReactorWarningMeltdown","ReactorWarningSCRAM" }; partial void InitProjSpecific(XElement element) { foreach (XElement subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "fissionratemeter": fissionRateMeter = new Sprite(subElement); break; case "turbineoutputmeter": turbineOutputMeter = new Sprite(subElement); break; case "meterpointer": meterPointer = new Sprite(subElement); break; case "sectorsprite": sectorSprite = new Sprite(subElement); break; case "tempmeterframe": tempMeterFrame = new Sprite(subElement); break; case "tempmeterbar": tempMeterBar = new Sprite(subElement); break; case "temprangeindicator": tempRangeIndicator = new Sprite(subElement); break; case "graphline": graphLine = new Sprite(subElement); break; } } var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.85f), GuiFrame.RectTransform, Anchor.Center), isHorizontal: true) { RelativeSpacing = 0.015f, Stretch = true }; GUIFrame columnLeft = new GUIFrame(new RectTransform(new Vector2(0.2f, 1.0f), paddedFrame.RectTransform), style: null); leftHUDColumn = columnLeft; GUIFrame columnMid = new GUIFrame(new RectTransform(new Vector2(0.45f, 1.0f), paddedFrame.RectTransform), style: null); GUIFrame columnRight = new GUIFrame(new RectTransform(new Vector2(0.35f, 1.0f), paddedFrame.RectTransform), style: null); //---------------------------------------------------------- //left column //---------------------------------------------------------- int buttonsPerRow = 2; int spacing = 5; int buttonWidth = columnLeft.Rect.Width / buttonsPerRow - (spacing * (buttonsPerRow - 1)); int buttonHeight = (int)(columnLeft.Rect.Height * 0.5f) / 4; for (int i = 0; i < warningTexts.Length; i++) { var warningBtn = new GUIButton(new RectTransform(new Point(buttonWidth, buttonHeight), columnLeft.RectTransform) { AbsoluteOffset = new Point((i % buttonsPerRow) * (buttonWidth + spacing), (int)Math.Floor(i / (float)buttonsPerRow) * (buttonHeight + spacing)) }, TextManager.Get(warningTexts[i]), style: "IndicatorButton"); var btnText = warningBtn.GetChild(); btnText.Font = GUI.SmallFont; btnText.Wrap = true; btnText.SetTextPos(); warningButtons.Add(warningTexts[i], warningBtn); } inventoryContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.45f), columnLeft.RectTransform, Anchor.BottomLeft), style: null); //---------------------------------------------------------- //mid column //---------------------------------------------------------- criticalHeatWarning = new GUITickBox(new RectTransform(new Point(30, 30), columnMid.RectTransform), TextManager.Get("ReactorWarningCriticalTemp"), font: GUI.SmallFont, style: "IndicatorLightRed") { CanBeFocused = false }; lowTemperatureWarning = new GUITickBox(new RectTransform(new Point(30, 30), columnMid.RectTransform) { RelativeOffset = new Vector2(0.3f, 0.0f) }, TextManager.Get("ReactorWarningCriticalLowTemp"), font: GUI.SmallFont, style: "IndicatorLightRed") { CanBeFocused = false }; criticalOutputWarning = new GUITickBox(new RectTransform(new Point(30, 30), columnMid.RectTransform) { RelativeOffset = new Vector2(0.75f, 0.0f) }, TextManager.Get("ReactorWarningCriticalOutput"), font: GUI.SmallFont, style: "IndicatorLightRed") { CanBeFocused = false }; new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.05f), columnMid.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.25f) }, TextManager.Get("ReactorFissionRate")); new GUICustomComponent(new RectTransform(new Vector2(0.5f, 0.5f), columnMid.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.3f) }, DrawFissionRateMeter, null) { ToolTip = TextManager.Get("ReactorTipFissionRate") }; new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.05f), columnMid.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.0f, 0.25f) }, TextManager.Get("ReactorTurbineOutput")); new GUICustomComponent(new RectTransform(new Vector2(0.5f, 0.5f), columnMid.RectTransform, Anchor.TopRight) { RelativeOffset = new Vector2(0.0f, 0.3f) }, DrawTurbineOutputMeter, null) { ToolTip = TextManager.Get("ReactorTipTurbineOutput") }; new GUITextBlock(new RectTransform(new Point(0, 20), columnMid.RectTransform, Anchor.BottomLeft) { AbsoluteOffset = new Point(0, 90) }, TextManager.Get("ReactorFissionRate")); fissionRateScrollBar = new GUIScrollBar(new RectTransform(new Point(columnMid.Rect.Width, 30), columnMid.RectTransform, Anchor.BottomCenter) { AbsoluteOffset = new Point(0, 60) }, style: "GUISlider", barSize: 0.1f) { OnMoved = (GUIScrollBar bar, float scrollAmount) => { LastUser = Character.Controlled; unsentChanges = true; targetFissionRate = scrollAmount * 100.0f; return false; } }; new GUITextBlock(new RectTransform(new Point(0, 20), columnMid.RectTransform, Anchor.BottomLeft) { AbsoluteOffset = new Point(0, 30) }, TextManager.Get("ReactorTurbineOutput")); turbineOutputScrollBar = new GUIScrollBar(new RectTransform(new Point(columnMid.Rect.Width, 30), columnMid.RectTransform, Anchor.BottomCenter), style: "GUISlider", barSize: 0.1f, isHorizontal: true) { OnMoved = (GUIScrollBar bar, float scrollAmount) => { LastUser = Character.Controlled; unsentChanges = true; targetTurbineOutput = scrollAmount * 100.0f; return false; } }; //---------------------------------------------------------- //right column //---------------------------------------------------------- new GUITextBlock(new RectTransform(new Point(100, 20), columnRight.RectTransform), TextManager.Get("ReactorAutoTemp")) { ToolTip = TextManager.Get("ReactorTipAutoTemp") }; autoTempSlider = new GUIScrollBar(new RectTransform(new Point(100, 30), columnRight.RectTransform) { AbsoluteOffset = new Point(0, 30) }, barSize: 0.5f, style: "OnOffSlider") { ToolTip = TextManager.Get("ReactorTipAutoTemp"), IsBooleanSwitch = true, BarScroll = 1.0f, OnMoved = (scrollBar, scrollAmount) => { LastUser = Character.Controlled; unsentChanges = true; return true; } }; onOffSwitch = new GUIScrollBar(new RectTransform(new Point(50, 80), columnRight.RectTransform, Anchor.TopRight), barSize: 0.2f, style: "OnOffLever") { IsBooleanSwitch = true, MinValue = 0.25f, MaxValue = 0.75f, OnMoved = (scrollBar, scrollAmount) => { LastUser = Character.Controlled; unsentChanges = true; return true; } }; var lever = onOffSwitch.GetChild(); lever.RectTransform.NonScaledSize = new Point(lever.Rect.Width + 30, lever.Rect.Height); var graphArea = new GUICustomComponent(new RectTransform(new Vector2(1.0f, 0.5f), columnRight.RectTransform, Anchor.BottomCenter) { AbsoluteOffset = new Point(0, 30) }, DrawGraph, null); var loadText = new GUITextBlock(new RectTransform(new Point(100, 30), graphArea.RectTransform, Anchor.TopLeft, Pivot.BottomLeft), "Load", textColor: Color.LightBlue, textAlignment: Alignment.CenterLeft) { ToolTip = TextManager.Get("ReactorTipLoad") }; string loadStr = TextManager.Get("ReactorLoad"); loadText.TextGetter += () => { return loadStr.Replace("[kw]", ((int)load).ToString()); }; var outputText = new GUITextBlock(new RectTransform(new Point(100, 30), graphArea.RectTransform, Anchor.BottomLeft, Pivot.TopLeft), "Output", textColor: Color.LightGreen, textAlignment: Alignment.CenterLeft) { ToolTip = TextManager.Get("ReactorTipPower") }; string outputStr = TextManager.Get("ReactorOutput"); outputText.TextGetter += () => { return outputStr.Replace("[kw]", ((int)-currPowerConsumption).ToString()); }; } public override void OnItemLoaded() { turbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f; fissionRateScrollBar.BarScroll = targetFissionRate / 100.0f; var itemContainer = item.GetComponent(); if (itemContainer != null) { itemContainer.AllowUIOverlap = true; itemContainer.Inventory.RectTransform = inventoryContainer.RectTransform; } } private void DrawGraph(SpriteBatch spriteBatch, GUICustomComponent container) { Rectangle graphArea = new Rectangle(container.Rect.X + 30, container.Rect.Y, container.Rect.Width - 30, container.Rect.Height); float maxLoad = loadGraph.Max(); float xOffset = graphTimer / updateGraphInterval; DrawGraph(outputGraph, spriteBatch, graphArea, Math.Max(10000.0f, maxLoad), xOffset, Color.LightGreen); DrawGraph(loadGraph, spriteBatch, graphArea, Math.Max(10000.0f, maxLoad), xOffset, Color.LightBlue); tempMeterFrame.Draw(spriteBatch, new Vector2(graphArea.X - 30, graphArea.Y), Color.White, Vector2.Zero, 0.0f, new Vector2(1.0f, graphArea.Height / tempMeterFrame.size.Y)); float tempFill = temperature / 100.0f; int barPadding = 5; Vector2 meterBarPos = new Vector2(graphArea.X - 30 + tempMeterFrame.size.X / 2, graphArea.Bottom - tempMeterBar.size.Y); while (meterBarPos.Y > graphArea.Bottom - graphArea.Height * tempFill) { float tempRatio = 1.0f - ((meterBarPos.Y - graphArea.Y) / graphArea.Height); Color color = tempRatio < 0.5f ? Color.Lerp(Color.Green, Color.Orange, tempRatio * 2.0f) : Color.Lerp(Color.Orange, Color.Red, (tempRatio - 0.5f) * 2.0f); tempMeterBar.Draw(spriteBatch, meterBarPos, color); meterBarPos.Y -= (tempMeterBar.size.Y + barPadding); } if (temperature > optimalTemperature.Y) { GUI.DrawRectangle(spriteBatch, new Vector2(graphArea.X - 30, graphArea.Y), new Vector2(tempMeterFrame.SourceRect.Width, (graphArea.Bottom - graphArea.Height * optimalTemperature.Y / 100.0f) - graphArea.Y), Color.Red * (float)Math.Sin(Timing.TotalTime * 5.0f) * 0.7f, isFilled: true); } if (temperature < optimalTemperature.X) { GUI.DrawRectangle(spriteBatch, new Vector2(graphArea.X - 30, graphArea.Bottom - graphArea.Height * optimalTemperature.X / 100.0f), new Vector2(tempMeterFrame.SourceRect.Width, graphArea.Bottom - (graphArea.Bottom - graphArea.Height * optimalTemperature.X / 100.0f)), Color.Red * (float)Math.Sin(Timing.TotalTime * 5.0f) * 0.7f, isFilled: true); } tempRangeIndicator.Draw(spriteBatch, new Vector2(meterBarPos.X, graphArea.Bottom - graphArea.Height * optimalTemperature.X / 100.0f)); tempRangeIndicator.Draw(spriteBatch, new Vector2(meterBarPos.X, graphArea.Bottom - graphArea.Height * optimalTemperature.Y / 100.0f)); } private void UpdateGraph(float deltaTime) { graphTimer += deltaTime * 1000.0f; if (graphTimer > updateGraphInterval) { UpdateGraph(outputGraph, -currPowerConsumption); UpdateGraph(loadGraph, load); graphTimer = 0.0f; } if (autoTemp) { fissionRateScrollBar.BarScroll = FissionRate / 100.0f; turbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f; } } private void DrawFissionRateMeter(SpriteBatch spriteBatch, GUICustomComponent container) { Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; spriteBatch.End(); spriteBatch.GraphicsDevice.ScissorRectangle = container.Rect; spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable); //make the pointer jitter a bit if it's at the upper limit of the fission rate float jitter = 0.0f; if (FissionRate > allowedFissionRate.Y - 5.0f) { float jitterAmount = Math.Min(targetFissionRate - allowedFissionRate.Y, 10.0f); float t = graphTimer / updateGraphInterval; jitter = (PerlinNoise.GetPerlin(t * 0.5f, t * 0.1f) - 0.5f) * jitterAmount; } DrawMeter(spriteBatch, container.Rect, fissionRateMeter, FissionRate + jitter, new Vector2(0.0f, 100.0f), optimalFissionRate, allowedFissionRate); spriteBatch.End(); spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; spriteBatch.Begin(SpriteSortMode.Deferred); } private void DrawTurbineOutputMeter(SpriteBatch spriteBatch, GUICustomComponent container) { DrawMeter(spriteBatch, container.Rect, turbineOutputMeter, TurbineOutput, new Vector2(0.0f, 100.0f), optimalTurbineOutput, allowedTurbineOutput); } public override void UpdateHUD(Character character, float deltaTime, Camera cam) { IsActive = true; bool lightOn = Timing.TotalTime % 0.5f < 0.25f && onOffSwitch.BarScroll < 0.5f; fissionRateScrollBar.Enabled = !autoTemp; turbineOutputScrollBar.Enabled = !autoTemp; criticalHeatWarning.Selected = temperature > allowedTemperature.Y && lightOn; lowTemperatureWarning.Selected = temperature < allowedTemperature.X && lightOn; criticalOutputWarning.Selected = -currPowerConsumption > load * 1.5f && lightOn; warningButtons["ReactorWarningOverheating"].Selected = temperature > optimalTemperature.Y && lightOn; warningButtons["ReactorWarningHighOutput"].Selected = -currPowerConsumption > load * 1.1f && lightOn; warningButtons["ReactorWarningLowTemp"].Selected = temperature < optimalTemperature.X && lightOn; warningButtons["ReactorWarningLowOutput"].Selected = -currPowerConsumption < load * 0.9f && lightOn; warningButtons["ReactorWarningFuelOut"].Selected = prevAvailableFuel < fissionRate * 0.01f && lightOn; warningButtons["ReactorWarningLowFuel"].Selected = prevAvailableFuel < fissionRate && lightOn; warningButtons["ReactorWarningMeltdown"].Selected = meltDownTimer > MeltdownDelay * 0.5f || item.Condition == 0.0f && lightOn; warningButtons["ReactorWarningSCRAM"].Selected = temperature > 0.1f && onOffSwitch.BarScroll > 0.5f; AutoTemp = autoTempSlider.BarScroll < 0.5f; shutDown = onOffSwitch.BarScroll > 0.5f; if (shutDown) { fissionRateScrollBar.BarScroll = FissionRate / 100.0f; turbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f; } } private bool ToggleAutoTemp(GUITickBox tickBox) { unsentChanges = true; autoTemp = tickBox.Selected; LastUser = Character.Controlled; return true; } private void DrawMeter(SpriteBatch spriteBatch, Rectangle rect, Sprite meterSprite, float value, Vector2 range, Vector2 optimalRange, Vector2 allowedRange) { float scale = Math.Min(rect.Width / meterSprite.size.X, rect.Height / meterSprite.size.Y); Vector2 pos = new Vector2(rect.Center.X, rect.Y + meterSprite.Origin.Y * scale); Vector2 optimalRangeNormalized = new Vector2( MathHelper.Clamp((optimalRange.X - range.X) / (range.Y - range.X), 0.0f, 0.95f), MathHelper.Clamp((optimalRange.Y - range.X) / (range.Y - range.X), 0.0f, 1.0f)); Vector2 allowedRangeNormalized = new Vector2( MathHelper.Clamp((allowedRange.X - range.X) / (range.Y - range.X), 0.0f, 0.95f), MathHelper.Clamp((allowedRange.Y - range.X) / (range.Y - range.X), 0.0f, 1.0f)); Vector2 sectorRad = new Vector2(-1.57f, 1.57f); Vector2 optimalSectorRad = new Vector2( MathHelper.Lerp(sectorRad.X, sectorRad.Y, optimalRangeNormalized.X), MathHelper.Lerp(sectorRad.X, sectorRad.Y, optimalRangeNormalized.Y)); Vector2 allowedSectorRad = new Vector2( MathHelper.Lerp(sectorRad.X, sectorRad.Y, allowedRangeNormalized.X), MathHelper.Lerp(sectorRad.X, sectorRad.Y, allowedRangeNormalized.Y)); if (optimalRangeNormalized.X == optimalRangeNormalized.Y) { sectorSprite.Draw(spriteBatch, pos, Color.Red, MathHelper.PiOver2, scale); } else { spriteBatch.End(); Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle(0, 0, GameMain.GraphicsWidth, (int)(pos.Y + (meterSprite.size.Y - meterSprite.Origin.Y) * scale) - 3); spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable); sectorSprite.Draw(spriteBatch, pos, Color.LightGreen, MathHelper.PiOver2 + (allowedSectorRad.X + allowedSectorRad.Y) / 2.0f, scale); sectorSprite.Draw(spriteBatch, pos, Color.Orange, optimalSectorRad.X, scale); sectorSprite.Draw(spriteBatch, pos, Color.Red, allowedSectorRad.X, scale); sectorSprite.Draw(spriteBatch, pos, Color.Orange, MathHelper.Pi + optimalSectorRad.Y, scale); sectorSprite.Draw(spriteBatch, pos, Color.Red, MathHelper.Pi + allowedSectorRad.Y, scale); spriteBatch.End(); spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; spriteBatch.Begin(SpriteSortMode.Deferred); } meterSprite.Draw(spriteBatch, pos, 0, scale); float normalizedValue = (value - range.X) / (range.Y - range.X); float valueRad = MathHelper.Lerp(sectorRad.X, sectorRad.Y, normalizedValue); meterPointer.Draw(spriteBatch, pos, valueRad, scale); } static void UpdateGraph(IList graph, T newValue) { for (int i = graph.Count - 1; i > 0; i--) { graph[i] = graph[i - 1]; } graph[0] = newValue; } private void DrawGraph(IList graph, SpriteBatch spriteBatch, Rectangle rect, float maxVal, float xOffset, Color color) { Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; spriteBatch.End(); spriteBatch.GraphicsDevice.ScissorRectangle = rect; spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable); float lineWidth = (float)rect.Width / (float)(graph.Count - 2); float yScale = (float)rect.Height / maxVal; GUI.DrawRectangle(spriteBatch, rect, Color.White); Vector2 prevPoint = new Vector2(rect.Right, rect.Bottom - (graph[1] + (graph[0] - graph[1]) * xOffset) * yScale); float currX = rect.Right - ((xOffset - 1.0f) * lineWidth); for (int i = 1; i < graph.Count - 1; i++) { currX -= lineWidth; Vector2 newPoint = new Vector2(currX, rect.Bottom - graph[i] * yScale); if (graphLine == null) { GUI.DrawLine(spriteBatch, prevPoint, newPoint - new Vector2(1.0f, 0), color); } else { Vector2 dir = Vector2.Normalize(newPoint - prevPoint); GUI.DrawLine(spriteBatch, graphLine.Texture, prevPoint - dir, newPoint + dir, color, 0, 5); } prevPoint = newPoint; } Vector2 lastPoint = new Vector2(rect.X, rect.Bottom - (graph[graph.Count - 1] + (graph[graph.Count - 2] - graph[graph.Count - 1]) * xOffset) * yScale); if (graphLine == null) { GUI.DrawLine(spriteBatch, prevPoint, lastPoint, color); } else { GUI.DrawLine(spriteBatch, graphLine.Texture, prevPoint, lastPoint + (lastPoint - prevPoint), color, 0, 5); } spriteBatch.End(); spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; spriteBatch.Begin(SpriteSortMode.Deferred); } protected override void RemoveComponentSpecific() { graphLine.Remove(); fissionRateMeter.Remove(); turbineOutputMeter.Remove(); meterPointer.Remove(); sectorSprite.Remove(); tempMeterFrame.Remove(); tempMeterBar.Remove(); tempRangeIndicator.Remove(); } public void ClientWrite(NetBuffer msg, object[] extraData = null) { msg.Write(autoTemp); msg.Write(shutDown); msg.WriteRangedSingle(targetFissionRate, 0.0f, 100.0f, 8); msg.WriteRangedSingle(targetTurbineOutput, 0.0f, 100.0f, 8); correctionTimer = CorrectionDelay; } public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) { if (correctionTimer > 0.0f) { StartDelayedCorrection(type, msg.ExtractBits(1 + 1 + 8 + 8 + 8 + 8), sendingTime); return; } AutoTemp = msg.ReadBoolean(); shutDown = msg.ReadBoolean(); Temperature = msg.ReadRangedSingle(0.0f, 100.0f, 8); targetFissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8); targetTurbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8); degreeOfSuccess = msg.ReadRangedSingle(0.0f, 1.0f, 8); fissionRateScrollBar.BarScroll = targetFissionRate / 100.0f; turbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f; onOffSwitch.BarScroll = shutDown ? Math.Max(onOffSwitch.BarScroll, 0.55f) : Math.Min(onOffSwitch.BarScroll, 0.45f); } } }