using Barotrauma.Extensions; using Barotrauma.Networking; using Barotrauma.Sounds; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Linq; namespace Barotrauma.Items.Components { partial class Reactor : Powered, IServerSerializable, IClientSerializable { public GUIButton AutoTempSwitch { get; private set; } public GUIButton PowerButton { get; private set; } private GUITickBox powerLight; private GUITickBox autoTempLight; private const int GraphSize = 25; private float graphTimer; private readonly int updateGraphInterval = 500; private Sprite fissionRateMeter, turbineOutputMeter; private Sprite meterPointer; private Sprite sectorSprite; private Sprite tempMeterFrame, tempMeterBar; private Sprite tempRangeIndicator; private Sprite graphLine; private GUICustomComponent graph; private GUIFrame inventoryWindow; private GUILayoutGroup buttonArea; private GUIFrame infographic; private Color optimalRangeColor = new Color(74,238,104,255); private Color offRangeColor = Color.Orange; private Color warningColor = Color.Red; private readonly Color[] temperatureColors = new Color[] { Color.Blue, Color.LightBlue, Color.Orange, Color.Red }; private Color outputColor = Color.Goldenrod; private Color loadColor = Color.LightSteelBlue; private RoundSound temperatureBoostSoundUp, temperatureBoostSoundDown; private GUIButton temperatureBoostUpButton, temperatureBoostDownButton; public GUIScrollBar FissionRateScrollBar { get; private set; } public GUIScrollBar TurbineOutputScrollBar { get; private set; } private readonly float[] outputGraph = new float[GraphSize]; private readonly float[] loadGraph = new float[GraphSize]; private GUITickBox criticalHeatWarning; private GUITickBox lowTemperatureWarning; private GUITickBox criticalOutputWarning; private GUIFrame inventoryContainer; private GUILayoutGroup paddedFrame; private readonly Dictionary warningButtons = new Dictionary(); private static readonly string[] warningTexts = new string[] { "ReactorWarningLowTemp", "ReactorWarningLowOutput", "ReactorWarningLowFuel", "ReactorWarningMeltdown", "ReactorWarningOverheating", "ReactorWarningHighOutput", "ReactorWarningFuelOut", "ReactorWarningSCRAM" }; public override bool RecreateGUIOnResolutionChange => true; public bool TriggerInfographic { get; set; } public bool IsInfographicVisible => infographic != null && infographic.Visible; partial void InitProjSpecific(ContentXElement element) { CreateGUI(); fissionRateMeter = new Sprite(element.GetChildElement("fissionratemeter")?.GetChildElement("sprite")); turbineOutputMeter = new Sprite(element.GetChildElement("turbineoutputmeter")?.GetChildElement("sprite")); meterPointer = new Sprite(element.GetChildElement("meterpointer")?.GetChildElement("sprite")); sectorSprite = new Sprite(element.GetChildElement("sectorsprite")?.GetChildElement("sprite")); tempMeterFrame = new Sprite(element.GetChildElement("tempmeterframe")?.GetChildElement("sprite")); tempMeterBar = new Sprite(element.GetChildElement("tempmeterbar")?.GetChildElement("sprite")); tempRangeIndicator = new Sprite(element.GetChildElement("temprangeindicator")?.GetChildElement("sprite")); graphLine = new Sprite(element.GetChildElement("graphline")?.GetChildElement("sprite")); foreach (var subElement in element.Elements()) { switch (subElement.Name.ToString().ToLowerInvariant()) { case "temperatureboostsoundup": temperatureBoostSoundUp = RoundSound.Load(subElement, false); break; case "temperatureboostsounddown": temperatureBoostSoundDown = RoundSound.Load(subElement, false); break; } } } protected override void CreateGUI() { warningButtons.Clear(); paddedFrame = new GUILayoutGroup(new RectTransform( GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, isHorizontal: true) { CanBeFocused = true, HoverCursor = CursorState.Default, AlwaysOverrideCursor = true, RelativeSpacing = 0.012f, Stretch = true }; GUILayoutGroup columnLeft = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), paddedFrame.RectTransform)) { RelativeSpacing = 0.012f, Stretch = true }; GUILayoutGroup columnRight = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), paddedFrame.RectTransform)) { CanBeFocused = true, RelativeSpacing = 0.012f, Stretch = true }; //---------------------------------------------------------- //left column //---------------------------------------------------------- inventoryWindow = new GUIFrame(new RectTransform(new Vector2(0.1f, 0.75f), GuiFrame.RectTransform, Anchor.TopLeft, Pivot.TopRight) { MinSize = new Point(85, 220), RelativeOffset = new Vector2(-0.02f, 0) }, style: "ItemUI"); GUILayoutGroup inventoryContent = new GUILayoutGroup(new RectTransform(inventoryWindow.Rect.Size - GUIStyle.ItemFrameMargin, inventoryWindow.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, childAnchor: Anchor.TopCenter) { Stretch = true }; /*new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), inventoryContent.RectTransform), "", textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont, wrap: true);*/ inventoryContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), inventoryContent.RectTransform), style: null); //---------------------------------------------------------- //mid column //---------------------------------------------------------- var topLeftArea = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.2f), columnLeft.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; Point maxIndicatorSize = new Point(int.MaxValue, (int)(40 * GUI.Scale)); criticalHeatWarning = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize }, TextManager.Get("ReactorWarningCriticalTemp"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightRed") { Selected = false, Enabled = false, ToolTip = TextManager.Get("ReactorHeatTip") }; criticalOutputWarning = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize }, TextManager.Get("ReactorWarningCriticalOutput"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightRed") { Selected = false, Enabled = false, ToolTip = TextManager.Get("ReactorOutputTip") }; lowTemperatureWarning = new GUITickBox(new RectTransform(new Vector2(0.4f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize }, TextManager.Get("ReactorWarningCriticalLowTemp"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightRed") { Selected = false, Enabled = false, ToolTip = TextManager.Get("ReactorTempTip") }; List indicatorLights = new List() { criticalHeatWarning, lowTemperatureWarning, criticalOutputWarning }; indicatorLights.ForEach(l => l.TextBlock.OverrideTextColor(GUIStyle.TextColorNormal)); topLeftArea.Recalculate(); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), columnLeft.RectTransform), style: "HorizontalLine"); float relativeYMargin = 0.02f; Vector2 relativeTextSize = new Vector2(0.9f, 0.15f); Vector2 sliderSize = new Vector2(1.0f, 0.125f); Vector2 meterSize = new Vector2(1, 1 - relativeTextSize.Y - relativeYMargin - sliderSize.Y - 0.1f); var meterArea = new GUIFrame(new RectTransform(new Vector2(1, 0.6f - relativeYMargin * 2), columnLeft.RectTransform), style: null); var leftArea = new GUIFrame(new RectTransform(new Vector2(0.49f, 1), meterArea.RectTransform), style: null); var rightArea = new GUIFrame(new RectTransform(new Vector2(0.49f, 1), meterArea.RectTransform, Anchor.TopCenter, Pivot.TopLeft), style: null); var fissionRateTextBox = new GUITextBlock(new RectTransform(relativeTextSize, leftArea.RectTransform, Anchor.TopCenter), TextManager.Get("ReactorFissionRate"), textColor: GUIStyle.TextColorNormal, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont) { AutoScaleHorizontal = true }; var fissionMeter = new GUICustomComponent(new RectTransform(meterSize, leftArea.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, relativeTextSize.Y + relativeYMargin) }, DrawFissionRateMeter, null) { ToolTip = TextManager.Get("ReactorTipFissionRate") }; var turbineOutputTextBox = new GUITextBlock(new RectTransform(relativeTextSize, rightArea.RectTransform, Anchor.TopCenter), TextManager.Get("ReactorTurbineOutput"), textColor: GUIStyle.TextColorNormal, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont) { AutoScaleHorizontal = true }; GUITextBlock.AutoScaleAndNormalize(turbineOutputTextBox, fissionRateTextBox); var turbineMeter = new GUICustomComponent(new RectTransform(meterSize, rightArea.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, relativeTextSize.Y + relativeYMargin) }, DrawTurbineOutputMeter, null) { ToolTip = TextManager.Get("ReactorTipTurbineOutput") }; FissionRateScrollBar = new GUIScrollBar(new RectTransform(sliderSize, leftArea.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0, fissionMeter.RectTransform.RelativeOffset.Y + meterSize.Y + relativeYMargin) }, style: "DeviceSlider", barSize: 0.15f) { Enabled = false, Step = 1.0f / 255, OnMoved = (GUIScrollBar bar, float scrollAmount) => { LastUser = Character.Controlled; unsentChanges = true; TargetFissionRate = scrollAmount * 100.0f; return false; } }; FissionRateScrollBar.Frame.UserData = UIHighlightAction.ElementId.FissionRateSlider; TurbineOutputScrollBar = new GUIScrollBar(new RectTransform(sliderSize, rightArea.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0, turbineMeter.RectTransform.RelativeOffset.Y + meterSize.Y + relativeYMargin) }, style: "DeviceSlider", barSize: 0.15f, isHorizontal: true) { Enabled = false, Step = 1.0f / 255, OnMoved = (GUIScrollBar bar, float scrollAmount) => { LastUser = Character.Controlled; unsentChanges = true; TargetTurbineOutput = scrollAmount * 100.0f; return false; } }; TurbineOutputScrollBar.Frame.UserData = UIHighlightAction.ElementId.TurbineOutputSlider; buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.2f), columnLeft.RectTransform)) { Stretch = true, RelativeSpacing = 0.02f }; var upperButtons = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.5f), buttonArea.RectTransform), isHorizontal: true) { Stretch = true, RelativeSpacing = 0.01f }; var lowerButtons = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.5f), buttonArea.RectTransform), isHorizontal: true) { Stretch = true, RelativeSpacing = 0.01f }; int buttonCount = warningTexts.Length; for (int i = 0; i < buttonCount; i++) { string text = warningTexts[i]; var b = new GUIButton(new RectTransform(Vector2.One, (i < 4) ? upperButtons.RectTransform : lowerButtons.RectTransform), TextManager.Get(text), style: "IndicatorButton") { Font = GUIStyle.SubHeadingFont, CanBeFocused = false }; warningButtons.Add(text, b); } upperButtons.Recalculate(); lowerButtons.Recalculate(); //only wrap texts that consist of multiple words and are way too big to fit otherwise warningButtons.Values.ForEach(b => b.TextBlock.Wrap = b.Text.Contains(' ') && b.TextBlock.TextSize.X > b.TextBlock.Rect.Width * 1.5f); GUITextBlock.AutoScaleAndNormalize(warningButtons.Values.Select(b => b.TextBlock)); //---------------------------------------------------------- //right column //---------------------------------------------------------- // Auto temp var topRightArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), columnRight.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true, RelativeSpacing = 0.02f }; topRightArea.RectTransform.MinSize = new Point(0, topLeftArea.Rect.Height); topRightArea.RectTransform.MaxSize = new Point(int.MaxValue, topLeftArea.Rect.Height); new GUIFrame(new RectTransform(new Vector2(0.01f, 1.0f), topRightArea.RectTransform), style: "VerticalLine"); AutoTempSwitch = new GUIButton(new RectTransform(new Vector2(0.15f, 0.9f), topRightArea.RectTransform), style: "SwitchVertical") { UserData = UIHighlightAction.ElementId.AutoTempSwitch, Enabled = false, Selected = AutoTemp, ClickSound = GUISoundType.UISwitch, OnClicked = (button, data) => { AutoTemp = !AutoTemp; LastUser = Character.Controlled; unsentChanges = true; return true; } }; AutoTempSwitch.RectTransform.MaxSize = new Point((int)(AutoTempSwitch.Rect.Height * 0.4f), int.MaxValue); autoTempLight = new GUITickBox(new RectTransform(new Vector2(0.4f, 1.0f), topRightArea.RectTransform), TextManager.Get("ReactorAutoTemp"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightYellow") { ToolTip = TextManager.Get("ReactorTipAutoTemp"), CanBeFocused = false, Selected = AutoTemp }; autoTempLight.RectTransform.MaxSize = new Point(int.MaxValue, criticalHeatWarning.Rect.Height); autoTempLight.TextBlock.OverrideTextColor(GUIStyle.TextColorNormal); new GUIFrame(new RectTransform(new Vector2(0.01f, 1.0f), topRightArea.RectTransform), style: "VerticalLine"); // Power button var powerArea = new GUIFrame(new RectTransform(new Vector2(0.4f, 1.0f), topRightArea.RectTransform), style: null); var paddedPowerArea = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), powerArea.RectTransform, Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: "PowerButtonFrame"); powerLight = new GUITickBox(new RectTransform(new Vector2(0.87f, 0.3f), paddedPowerArea.RectTransform, Anchor.TopCenter, Pivot.Center), TextManager.Get("PowerLabel"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightPower") { CanBeFocused = false, Selected = _powerOn }; powerLight.TextBlock.Padding = new Vector4(5.0f, 0.0f, 0.0f, 0.0f); powerLight.TextBlock.AutoScaleHorizontal = true; powerLight.TextBlock.OverrideTextColor(GUIStyle.TextColorNormal); PowerButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.75f), paddedPowerArea.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0, 0.1f) }, style: "PowerButton") { UserData = UIHighlightAction.ElementId.PowerButton, OnClicked = (button, data) => { PowerOn = !PowerOn; LastUser = Character.Controlled; unsentChanges = true; return true; } }; topRightArea.Recalculate(); autoTempLight.TextBlock.Padding = new Vector4(autoTempLight.TextBlock.Padding.X, 0.0f, 0.0f, 0.0f); autoTempLight.TextBlock.Text = autoTempLight.TextBlock.Text.Replace(" ", "\n"); autoTempLight.TextBlock.AutoScaleHorizontal = true; GUITextBlock.AutoScaleAndNormalize(indicatorLights.Select(l => l.TextBlock)); // right bottom (graph area) ----------------------- new GUIFrame(new RectTransform(new Vector2(0.95f, 0.01f), columnRight.RectTransform), style: "HorizontalLine"); var bottomRightArea = new GUILayoutGroup(new RectTransform(Vector2.One, columnRight.RectTransform), isHorizontal: true) { Stretch = true, CanBeFocused = true, RelativeSpacing = 0.02f }; new GUIFrame(new RectTransform(new Vector2(0.01f, 1.0f), bottomRightArea.RectTransform), style: "VerticalLine"); var temperatureArea = new GUILayoutGroup(new RectTransform(new Vector2(0.1f, 1), bottomRightArea.RectTransform, Anchor.Center), isHorizontal: false) { Stretch = true, RelativeSpacing = 0.01f }; temperatureBoostUpButton = new GUIButton(new RectTransform(Vector2.One, temperatureArea.RectTransform, scaleBasis: ScaleBasis.BothWidth), style: "GUIPlusButton") { ToolTip = TextManager.Get("reactor.temperatureboostup"), OnClicked = (_, __) => { unsentChanges = true; sendUpdateTimer = 0.0f; ApplyTemperatureBoost(TemperatureBoostAmount); return true; } }; new GUICustomComponent(new RectTransform(Vector2.One, temperatureArea.RectTransform, Anchor.Center), DrawTempMeter, null); temperatureBoostDownButton = new GUIButton(new RectTransform(Vector2.One, temperatureArea.RectTransform, scaleBasis: ScaleBasis.BothWidth), style: "GUIMinusButton") { ToolTip = TextManager.Get("reactor.temperatureboostdown"), OnClicked = (_, __) => { unsentChanges = true; sendUpdateTimer = 0.0f; ApplyTemperatureBoost(-TemperatureBoostAmount); return true; } }; var graphArea = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), bottomRightArea.RectTransform)) { Stretch = true, RelativeSpacing = 0.02f }; relativeTextSize = new Vector2(1.0f, 0.15f); var loadText = new GUITextBlock(new RectTransform(relativeTextSize, graphArea.RectTransform), "Load", textColor: loadColor, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft) { ToolTip = TextManager.Get("ReactorTipLoad") }; LocalizedString loadStr = TextManager.Get("ReactorLoad"); LocalizedString kW = TextManager.Get("kilowatt"); loadText.TextGetter += () => $"{loadStr.Replace("[kw]", ((int)Load).ToString())} {kW}"; var graphFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), graphArea.RectTransform), style: "InnerFrameRed"); graph = new GUICustomComponent(new RectTransform(new Vector2(0.9f, 0.98f), graphFrame.RectTransform, Anchor.Center), DrawGraph, null); var outputText = new GUITextBlock(new RectTransform(relativeTextSize, graphArea.RectTransform), "Output", textColor: outputColor, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft) { ToolTip = TextManager.Get("ReactorTipPower") }; LocalizedString outputStr = TextManager.Get("ReactorOutput"); outputText.TextGetter += () => $"{outputStr.Replace("[kw]", ((int)-currPowerConsumption).ToString())} {kW}"; InitInventoryUI(); // Infographic overlay --------------------- int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f); var helpButtonRt = new RectTransform(new Point(buttonHeight), parent: GuiFrame.RectTransform, anchor: Anchor.TopRight) { AbsoluteOffset = new Point(buttonHeight / 4), MinSize = new Point(buttonHeight) }; new GUIButton(helpButtonRt, "", style: "HelpIcon") { OnClicked = (_, _) => { CreateInfrographic(); return true; } }; } private void ApplyTemperatureBoost(float amount) { if (Math.Abs(temperatureBoost) <= TemperatureBoostAmount * 0.9f && Math.Abs(amount) > TemperatureBoostAmount * 0.9f) { var sound = amount > 0 ? temperatureBoostSoundUp : temperatureBoostSoundDown; if (sound != null) { SoundPlayer.PlaySound( sound, item.WorldPosition, hullGuess: item.CurrentHull); } } temperatureBoost = amount; } private void InitInventoryUI() { var itemContainer = item.GetComponent(); if (itemContainer != null) { itemContainer.UILabel = ""; itemContainer.AllowUIOverlap = true; itemContainer.Inventory.RectTransform = inventoryContainer.RectTransform; } } public override void OnItemLoaded() { base.OnItemLoaded(); TurbineOutputScrollBar.BarScroll = TargetTurbineOutput / 100.0f; FissionRateScrollBar.BarScroll = TargetFissionRate / 100.0f; InitInventoryUI(); } private void DrawTempMeter(SpriteBatch spriteBatch, GUICustomComponent container) { Vector2 meterPos = new Vector2(container.Rect.X, container.Rect.Y); Vector2 meterScale = new Vector2(container.Rect.Width / (float)tempMeterFrame.SourceRect.Width, container.Rect.Height / (float)tempMeterFrame.SourceRect.Height); tempMeterFrame.Draw(spriteBatch, meterPos, Color.White, tempMeterFrame.Origin, 0.0f, scale: meterScale); float tempFill = temperature / 100.0f; float meterBarScale = container.Rect.Width / (float)tempMeterBar.SourceRect.Width; Vector2 meterBarPos = new Vector2(container.Center.X, container.Rect.Bottom - tempMeterBar.size.Y * meterBarScale - (int)(5 * GUI.yScale)); while (meterBarPos.Y > container.Rect.Bottom + (int)(5 * GUI.yScale) - container.Rect.Height * tempFill) { float tempRatio = 1.0f - ((meterBarPos.Y - container.Rect.Y) / container.Rect.Height); Color color = ToolBox.GradientLerp(tempRatio, temperatureColors); tempMeterBar.Draw(spriteBatch, meterBarPos, color: color, scale: meterBarScale); int spacing = 2; meterBarPos.Y -= tempMeterBar.size.Y * meterBarScale + spacing; } if (temperature > optimalTemperature.Y) { GUI.DrawRectangle(spriteBatch, meterPos, new Vector2(container.Rect.Width, (container.Rect.Bottom - container.Rect.Height * optimalTemperature.Y / 100.0f) - container.Rect.Y), warningColor * (float)Math.Sin(Timing.TotalTime * 5.0f) * 0.7f, isFilled: true); } if (temperature < optimalTemperature.X) { GUI.DrawRectangle(spriteBatch, new Vector2(meterPos.X, container.Rect.Bottom - container.Rect.Height * optimalTemperature.X / 100.0f), new Vector2(container.Rect.Width, container.Rect.Bottom - (container.Rect.Bottom - container.Rect.Height * optimalTemperature.X / 100.0f)), warningColor * (float)Math.Sin(Timing.TotalTime * 5.0f) * 0.7f, isFilled: true); } float tempRangeIndicatorScale = container.Rect.Width / (float)tempRangeIndicator.SourceRect.Width; tempRangeIndicator.Draw(spriteBatch, new Vector2(container.Center.X, container.Rect.Bottom - container.Rect.Height * optimalTemperature.X / 100.0f), Color.White, tempRangeIndicator.Origin, 0, scale: tempRangeIndicatorScale); tempRangeIndicator.Draw(spriteBatch, new Vector2(container.Center.X, container.Rect.Bottom - container.Rect.Height * optimalTemperature.Y / 100.0f), Color.White, tempRangeIndicator.Origin, 0, scale: tempRangeIndicatorScale); } private void DrawGraph(SpriteBatch spriteBatch, GUICustomComponent container) { if (item.Removed) { return; } float maxLoad = loadGraph.Max(); float xOffset = graphTimer / updateGraphInterval; Rectangle graphRect = new Rectangle(container.Rect.X, container.Rect.Y, container.Rect.Width, container.Rect.Height - (int)(5 * GUI.yScale)); DrawGraph(outputGraph, spriteBatch, graphRect, Math.Max(10000.0f, maxLoad), xOffset, outputColor); DrawGraph(loadGraph, spriteBatch, graphRect, Math.Max(10000.0f, maxLoad), xOffset, loadColor); } 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) { if (item.Removed) { return; } 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) { if (item.Removed) { return; } Vector2 clampedOptimalTurbineOutput = optimalTurbineOutput; Vector2 clampedAllowedTurbineOutput = allowedTurbineOutput; if (clampedOptimalTurbineOutput.X > 100.0f) { clampedOptimalTurbineOutput = new Vector2(92.0f, 110.0f); clampedAllowedTurbineOutput = new Vector2(85.0f, 110.0f); } DrawMeter(spriteBatch, container.Rect, turbineOutputMeter, TurbineOutput, new Vector2(0.0f, 100.0f), clampedOptimalTurbineOutput, clampedAllowedTurbineOutput); } public override void UpdateHUDComponentSpecific(Character character, float deltaTime, Camera cam) { IsActive = true; bool lightOn = Timing.TotalTime % 0.5f < 0.25f && PowerOn; 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 && !PowerOn; if (paddedFrame.Rect.Contains(PlayerInput.MousePosition) && !PlayerInput.KeyDown(InputType.Deselect) && !PlayerInput.KeyHit(InputType.Deselect)) { Character.DisableControls = true; } if (!PowerOn) { FissionRateScrollBar.BarScroll = FissionRate / 100.0f; TurbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f; } else if (!autoTemp && Character.DisableControls && GUI.KeyboardDispatcher.Subscriber == null) { Vector2 input = Vector2.Zero; float rate = 50.0f; //percentage per second if (PlayerInput.KeyDown(InputType.Left)) input.X += -1.0f; if (PlayerInput.KeyDown(InputType.Right)) input.X += 1.0f; if (PlayerInput.KeyDown(InputType.Up)) input.Y += 1.0f; if (PlayerInput.KeyDown(InputType.Down)) input.Y += -1.0f; if (PlayerInput.KeyDown(InputType.Run)) rate = 200.0f; else if (PlayerInput.KeyDown(InputType.Crouch)) rate = 20.0f; rate *= deltaTime; input.X *= rate; input.Y *= rate; if (input.LengthSquared() > 0) { LastUser = Character.Controlled; unsentChanges = true; if (input.X != 0.0f && GUIScrollBar.DraggingBar != FissionRateScrollBar) { TargetFissionRate = MathHelper.Clamp(TargetFissionRate + input.X, 0.0f, 100.0f); FissionRateScrollBar.BarScroll += input.X / 100.0f; } if (input.Y != 0.0f && GUIScrollBar.DraggingBar != TurbineOutputScrollBar) { TargetTurbineOutput = MathHelper.Clamp(TargetTurbineOutput + input.Y, 0.0f, 100.0f); TurbineOutputScrollBar.BarScroll += input.Y / 100.0f; } } } if (GuiFrame is not null && GuiFrame.Visible && TriggerInfographic) { CreateInfrographic(); TriggerInfographic = false; } } 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.35f, 1.35f); 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)); Vector2 pointerPos = pos - new Vector2(0, 30) * scale; float scaleMultiplier = 0.95f; if (optimalRangeNormalized.X == optimalRangeNormalized.Y) { sectorSprite.Draw(spriteBatch, pointerPos, GUIStyle.Red, MathHelper.PiOver2, scale * scaleMultiplier); } else { spriteBatch.End(); Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle(0, 0, GameMain.GraphicsWidth, (int)(pointerPos.Y + (meterSprite.size.Y - meterSprite.Origin.Y) * scale) - 3); spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable); sectorSprite.Draw(spriteBatch, pointerPos, optimalRangeColor, MathHelper.PiOver2 + (allowedSectorRad.X + allowedSectorRad.Y) / 2.0f, scale * scaleMultiplier); sectorSprite.Draw(spriteBatch, pointerPos, offRangeColor, optimalSectorRad.X, scale * scaleMultiplier); sectorSprite.Draw(spriteBatch, pointerPos, warningColor, allowedSectorRad.X, scale * scaleMultiplier); sectorSprite.Draw(spriteBatch, pointerPos, offRangeColor, MathHelper.Pi + optimalSectorRad.Y, scale * scaleMultiplier); sectorSprite.Draw(spriteBatch, pointerPos, warningColor, MathHelper.Pi + allowedSectorRad.Y, scale * scaleMultiplier); 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, pointerPos, 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; 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?.Texture == 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?.Texture == 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); } private enum InfographicArrowStyle { Straight, Curved }; private void CreateInfrographic() { if (infographic != null) { return; } var dimColor = Color.Lerp(Color.Black, Color.TransparentBlack, 0.25f); // Dim reactor interface infographic = new GUIFrame(new RectTransform(Vector2.One, GuiFrame.RectTransform)) { CanBeFocused = false, Color = dimColor }; // Dim inventory window new GUIFrame(new RectTransform(inventoryWindow.Rect.Size, infographic.RectTransform) { AbsoluteOffset = inventoryWindow.Rect.Location - GuiFrame.Rect.Location}, color: dimColor) { CanBeFocused = false }; int arrowSize = (int)(70 * GUI.Scale); var arrows = new Dictionary() { { "fuelslots", CreateArrow(InfographicArrowStyle.Curved, inventoryWindow, Anchor.TopLeft, Pivot.TopRight, SpriteEffects.FlipVertically) }, { "temperature", CreateArrow(InfographicArrowStyle.Straight, temperatureBoostDownButton, Anchor.Center, Pivot.Center) }, { "automaticcontrol", CreateArrow(InfographicArrowStyle.Curved, AutoTempSwitch, Anchor.TopRight, Pivot.BottomRight, rotationDegrees: 90f) }, { "power", CreateArrow(InfographicArrowStyle.Straight, PowerButton, Anchor.BottomCenter, Pivot.TopCenter) } }; CreateArrow(InfographicArrowStyle.Straight, FissionRateScrollBar, Anchor.Center, Pivot.Center); CreateArrow(InfographicArrowStyle.Straight, TurbineOutputScrollBar, Anchor.Center, Pivot.Center); CreateArrow(InfographicArrowStyle.Straight, graph, Anchor.TopLeft, Pivot.TopLeft, SpriteEffects.FlipHorizontally, additionalOffset: new Point(arrowSize / 2, 0)); CreateArrow(InfographicArrowStyle.Straight, graph, Anchor.BottomLeft, Pivot.BottomLeft, SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically, additionalOffset: new Point(arrowSize / 2, 0)); new GUICustomComponent(new RectTransform(Vector2.One, infographic.RectTransform), onDraw: (sb, c) => { DrawToolTip("fuelslots", Anchor.TopLeft, Pivot.BottomCenter, arrows["fuelslots"]); DrawToolTip("fissionrate", Anchor.TopCenter, Pivot.TopCenter, buttonArea); DrawToolTip("temperature", Anchor.BottomLeft, Pivot.TopLeft, arrows["temperature"]); DrawToolTip("automaticcontrol", Anchor.TopLeft, Pivot.CenterRight, arrows["automaticcontrol"]); DrawToolTip("power", Anchor.BottomCenter, Pivot.TopCenter, arrows["power"]); DrawToolTip("load", Anchor.CenterLeft, Pivot.CenterLeft, graph); void DrawToolTip(string textTag, Anchor anchor, Pivot pivot, GUIComponent targetComponent) { GUIComponent.DrawToolTip(sb, TextManager.Get($"infographic.reactor.{textTag}"), targetComponent.Rect, anchor: anchor, pivot: pivot); } }) { CanBeFocused = false }; var closeButtonRt = new RectTransform(new Point(200, 50).Multiply(GUI.Scale), infographic.RectTransform, Anchor.TopRight, Pivot.BottomRight) { AbsoluteOffset = new Point(0, -50).Multiply(GUI.Scale) }; new GUIButton(closeButtonRt, TextManager.Get("closeinfographic")) { UserData = UIHighlightAction.ElementId.CloseButton, OnClicked = (_, _) => { CloseInfographic(Character.Controlled); return true; } }; item.OnDeselect += CloseInfographic; GUIImage CreateArrow(InfographicArrowStyle arrowStyle, GUIComponent parent, Anchor anchor, Pivot pivot, SpriteEffects spriteEffects = SpriteEffects.None, float rotationDegrees = 0f, Point? additionalOffset = null) { Point offset = (additionalOffset ?? Point.Zero) + RectTransform.CalculateAnchorPoint(anchor, parent.Rect) - GuiFrame.Rect.Location; var rt = new RectTransform(new Point(arrowSize), infographic.RectTransform, pivot: pivot) { AbsoluteOffset = offset }; string style = arrowStyle == InfographicArrowStyle.Straight ? "InfographicArrow" : "InfographicArrowCurved"; return new GUIImage(rt, style) { CanBeFocused = false, Rotation = MathHelper.ToRadians(rotationDegrees), SpriteEffects = spriteEffects }; } void CloseInfographic(Character character) { if (character != Character.Controlled) { return; } GuiFrame.RemoveChild(infographic); infographic = null; item.OnDeselect -= CloseInfographic; } } protected override void RemoveComponentSpecific() { base.RemoveComponentSpecific(); graphLine?.Remove(); fissionRateMeter?.Remove(); turbineOutputMeter?.Remove(); meterPointer?.Remove(); sectorSprite?.Remove(); tempMeterFrame?.Remove(); tempMeterBar?.Remove(); tempRangeIndicator?.Remove(); } public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) { msg.WriteBoolean(autoTemp); msg.WriteBoolean(PowerOn); msg.WriteRangedSingle(TargetFissionRate, 0.0f, 100.0f, 8); msg.WriteRangedSingle(TargetTurbineOutput, 0.0f, 100.0f, 8); msg.WriteRangedSingle(temperatureBoost, -TemperatureBoostAmount, TemperatureBoostAmount, 8); correctionTimer = CorrectionDelay; } public void ClientEventRead(IReadMessage msg, float sendingTime) { if (correctionTimer > 0.0f) { StartDelayedCorrection(msg.ExtractBits(1 + 1 + 8 + 8 + 8 + 8 + 8), sendingTime); return; } AutoTemp = msg.ReadBoolean(); PowerOn = 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); ApplyTemperatureBoost(msg.ReadRangedSingle(-TemperatureBoostAmount, TemperatureBoostAmount, 8)); if (Math.Abs(FissionRateScrollBar.BarScroll - TargetFissionRate / 100.0f) > 0.01f) { FissionRateScrollBar.BarScroll = TargetFissionRate / 100.0f; } if (Math.Abs(TurbineOutputScrollBar.BarScroll - TargetTurbineOutput / 100.0f) > 0.01f) { TurbineOutputScrollBar.BarScroll = TargetTurbineOutput / 100.0f; } IsActive = true; } private void UpdateUIElementStates() { if (powerLight != null) { powerLight.Selected = _powerOn; } if (AutoTempSwitch != null) { AutoTempSwitch.Selected = autoTemp; AutoTempSwitch.Enabled = _powerOn; } if (autoTempLight != null) { autoTempLight.Selected = autoTemp && _powerOn; } if (FissionRateScrollBar != null) { FissionRateScrollBar.Enabled = _powerOn && !autoTemp; } if (TurbineOutputScrollBar != null) { TurbineOutputScrollBar.Enabled = _powerOn && !autoTemp; } } } }