diff --git a/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HeatParts/GridHeatApi.cs b/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HeatParts/GridHeatApi.cs new file mode 100644 index 000000000..667ab13ae --- /dev/null +++ b/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HeatParts/GridHeatApi.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using Sandbox.ModAPI; +using VRage.Game.ModAPI; + +namespace Epstein_Fusion_DS.HeatParts +{ + /// + /// Copy this class into another mod and call Load() in LoadData() to use the grid heat API. + /// + public sealed class GridHeatApi + { + private const long Channel = 11920040L; + private const string ApiRequest = "GridHeatAPI.Request"; + + private Action _onReady; + private Dictionary _api; + private bool _registered; + + public bool IsReady => _api != null; + + public void Load(Action onReady = null) + { + if (_registered) + return; + + _onReady = onReady; + _registered = true; + MyAPIGateway.Utilities.RegisterMessageHandler(Channel, HandleMessage); + MyAPIGateway.Utilities.SendModMessage(Channel, ApiRequest); + } + + public void Unload() + { + if (!_registered) + return; + + MyAPIGateway.Utilities.UnregisterMessageHandler(Channel, HandleMessage); + _registered = false; + _api = null; + _onReady = null; + } + + public int Version() + { + var method = Get>("Version"); + return method?.Invoke() ?? 0; + } + + public bool AddHeat(IMyCubeGrid grid, float heat) + { + var method = Get>("AddHeat"); + return method != null && method(grid, heat); + } + + public bool SetHeat(IMyCubeGrid grid, float heat) + { + var method = Get>("SetHeat"); + return method != null && method(grid, heat); + } + + public float GetHeat(IMyCubeGrid grid) + { + var method = Get>("GetHeat"); + return method?.Invoke(grid) ?? -1; + } + + public float GetHeatRatio(IMyCubeGrid grid) + { + var method = Get>("GetHeatRatio"); + return method?.Invoke(grid) ?? -1; + } + + public float GetHeatCapacity(IMyCubeGrid grid) + { + var method = Get>("GetHeatCapacity"); + return method?.Invoke(grid) ?? -1; + } + + public float GetHeatDissipation(IMyCubeGrid grid) + { + var method = Get>("GetHeatDissipation"); + return method?.Invoke(grid) ?? -1; + } + + public float GetHeatGeneration(IMyCubeGrid grid) + { + var method = Get>("GetHeatGeneration"); + return method?.Invoke(grid) ?? -1; + } + + public bool SetHeatCapacity(IMyCubeGrid grid, float capacity) + { + var method = Get>("SetHeatCapacity"); + return method != null && method(grid, capacity); + } + + public bool SetHeatDissipation(IMyCubeGrid grid, float dissipation) + { + var method = Get>("SetHeatDissipation"); + return method != null && method(grid, dissipation); + } + + public bool SetHeatGeneration(IMyCubeGrid grid, float generation) + { + var method = Get>("SetHeatGeneration"); + return method != null && method(grid, generation); + } + + public void SetHudVisible(bool visible) + { + Get>("SetHudVisible")?.Invoke(visible); + } + + public void SetHudOffset(float x, float y, float z) + { + Get>("SetHudOffset")?.Invoke(x, y, z); + } + + private T Get(string name) where T : class + { + if (_api == null || !_api.ContainsKey(name)) + return null; + + return _api[name] as T; + } + + private void HandleMessage(object obj) + { + var api = obj as Dictionary; + if (api == null) + return; + + _api = api; + _onReady?.Invoke(); + _onReady = null; + } + } +} diff --git a/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HeatParts/GridHeatManager.cs b/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HeatParts/GridHeatManager.cs index 34e874fc3..18c564696 100644 --- a/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HeatParts/GridHeatManager.cs +++ b/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HeatParts/GridHeatManager.cs @@ -21,6 +21,12 @@ internal class GridHeatManager public float HeatGeneration; + public float ApiHeatCapacity; + + public float ApiHeatDissipation; + + public float ApiHeatGeneration; + public float HeatRatio = float.Epsilon; public float HeatStored; @@ -34,7 +40,12 @@ public GridHeatManager(IMyCubeGrid grid) public MyCubeGrid Grid { get; } - public float GrossHeatDissipation => (HeatDissipation + BaseHeatDissipation) * HeatRatio; + public float TotalHeatCapacity => HeatCapacity + BaseHeatCapacity + ApiHeatCapacity; + + public float GrossHeatDissipation => (HeatDissipation + BaseHeatDissipation + ApiHeatDissipation) * HeatRatio; + + public bool HasApiHeat => + ApiHeatCapacity > 0 || ApiHeatDissipation > 0 || ApiHeatGeneration != 0 || HeatStored > 0; public void UpdateTick() { @@ -42,7 +53,7 @@ public void UpdateTick() Update15Tick(); _ticks++; - if (HeatCapacity + BaseHeatCapacity == 0) + if (TotalHeatCapacity == 0) { HeatRatio = 1; HeatStored = 0; @@ -52,10 +63,28 @@ public void UpdateTick() HeatStored += (HeatGeneration - GrossHeatDissipation) / 60; if (HeatStored < 0) HeatStored = 0; - else if (HeatStored > HeatCapacity + BaseHeatCapacity) - HeatStored = HeatCapacity + BaseHeatCapacity; + else if (HeatStored > TotalHeatCapacity) + HeatStored = TotalHeatCapacity; + + HeatRatio = HeatStored / TotalHeatCapacity; + } + + public float AddHeat(float heat) + { + return SetHeat(HeatStored + heat); + } + + public float SetHeat(float heat) + { + if (heat < 0) + heat = 0; + + if (TotalHeatCapacity > 0 && heat > TotalHeatCapacity) + heat = TotalHeatCapacity; - HeatRatio = HeatStored / (HeatCapacity + BaseHeatCapacity); + HeatStored = heat; + HeatRatio = TotalHeatCapacity > 0 ? HeatStored / TotalHeatCapacity : HeatStored > 0 ? 1 : float.Epsilon; + return HeatStored; } public void RemoveAssembly(int assemblyId) @@ -71,7 +100,7 @@ private void Update15Tick() BaseHeatDissipation = 2 * (gridSize.X * gridSize.Y + gridSize.Y * gridSize.Z + gridSize.Z * gridSize.X) * BaseHeatDissipationModifier; - HeatGeneration = 0; + HeatGeneration = ApiHeatGeneration; foreach (var assemblyId in ModularApi.GetGridAssemblies(Grid)) HeatGeneration += ModularApi.GetAssemblyProperty(assemblyId, @@ -106,4 +135,4 @@ public void OnPartRemove(int assemblyId, IMyCubeBlock block, bool isBaseBlock) } } } -} \ No newline at end of file +} diff --git a/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HeatParts/HeatManager.cs b/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HeatParts/HeatManager.cs index d770a90f5..dbfd3efb1 100644 --- a/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HeatParts/HeatManager.cs +++ b/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HeatParts/HeatManager.cs @@ -1,31 +1,46 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Sandbox.ModAPI; using VRage.Game.ModAPI; using VRage.ModAPI; +using VRageMath; namespace Epstein_Fusion_DS.HeatParts { internal class HeatManager { + public const long ApiChannel = 11920040L; + private const string ApiRequest = "GridHeatAPI.Request"; + public static HeatManager I = new HeatManager(); private readonly Dictionary _heatSystems = new Dictionary(); + private Dictionary _api; + + public bool HeatHudVisible = true; + public Vector3D HeatHudOffset = new Vector3D(-0.759375, -0.8, 0); + public void Load() { I = this; MyAPIGateway.Entities.OnEntityAdd += OnEntityAdd; MyAPIGateway.Entities.OnEntityRemove += OnEntityRemove; + MyAPIGateway.Utilities.RegisterMessageHandler(ApiChannel, HandleApiRequest); + RegisterExistingGrids(); } public void Unload() { MyAPIGateway.Entities.OnEntityAdd -= OnEntityAdd; MyAPIGateway.Entities.OnEntityRemove -= OnEntityRemove; + MyAPIGateway.Utilities.UnregisterMessageHandler(ApiChannel, HandleApiRequest); foreach (var system in _heatSystems.Values) system.Unload(); + _heatSystems.Clear(); + _api = null; I = null; } @@ -46,9 +61,14 @@ public float GetGridHeatLevel(IMyCubeGrid grid) return _heatSystems.GetValueOrDefault(grid, null)?.HeatRatio ?? -1; } + public float GetGridHeat(IMyCubeGrid grid) + { + return _heatSystems.GetValueOrDefault(grid, null)?.HeatStored ?? -1; + } + public float GetGridHeatCapacity(IMyCubeGrid grid) { - return _heatSystems.GetValueOrDefault(grid, null)?.HeatCapacity ?? -1; + return _heatSystems.GetValueOrDefault(grid, null)?.TotalHeatCapacity ?? -1; } public float GetGridHeatDissipation(IMyCubeGrid grid) @@ -61,13 +81,117 @@ public float GetGridHeatGeneration(IMyCubeGrid grid) return _heatSystems.GetValueOrDefault(grid, null)?.HeatGeneration ?? -1; } + public bool HasGridHeat(IMyCubeGrid grid) + { + return _heatSystems.GetValueOrDefault(grid, null)?.HasApiHeat ?? false; + } + + public bool AddGridHeat(IMyCubeGrid grid, float heat) + { + var system = EnsureGrid(grid); + if (system == null) + return false; + + system.AddHeat(heat); + return true; + } + + public bool SetGridHeat(IMyCubeGrid grid, float heat) + { + var system = EnsureGrid(grid); + if (system == null) + return false; + + system.SetHeat(heat); + return true; + } + + public bool SetGridHeatCapacity(IMyCubeGrid grid, float capacity) + { + var system = EnsureGrid(grid); + if (system == null) + return false; + + system.ApiHeatCapacity = Math.Max(0, capacity); + system.SetHeat(system.HeatStored); + return true; + } + + public bool SetGridHeatDissipation(IMyCubeGrid grid, float dissipation) + { + var system = EnsureGrid(grid); + if (system == null) + return false; + + system.ApiHeatDissipation = Math.Max(0, dissipation); + return true; + } + + public bool SetGridHeatGeneration(IMyCubeGrid grid, float generation) + { + var system = EnsureGrid(grid); + if (system == null) + return false; + + system.ApiHeatGeneration = generation; + system.HeatGeneration = generation; + return true; + } + + public void SetHeatHudVisible(bool visible) + { + HeatHudVisible = visible; + } + + public void SetHeatHudOffset(float x, float y, float z) + { + HeatHudOffset = new Vector3D(x, y, z); + } + + private void HandleApiRequest(object obj) + { + if (!(obj is string) || (string)obj != ApiRequest) + return; + + MyAPIGateway.Utilities.SendModMessage(ApiChannel, GetApi()); + } + + private Dictionary GetApi() + { + return _api ?? (_api = new Dictionary + { + ["Version"] = new Func(() => 1), + ["AddHeat"] = new Func(AddGridHeat), + ["SetHeat"] = new Func(SetGridHeat), + ["GetHeat"] = new Func(GetGridHeat), + ["GetHeatRatio"] = new Func(GetGridHeatLevel), + ["GetHeatCapacity"] = new Func(GetGridHeatCapacity), + ["GetHeatDissipation"] = new Func(GetGridHeatDissipation), + ["GetHeatGeneration"] = new Func(GetGridHeatGeneration), + ["SetHeatCapacity"] = new Func(SetGridHeatCapacity), + ["SetHeatDissipation"] = new Func(SetGridHeatDissipation), + ["SetHeatGeneration"] = new Func(SetGridHeatGeneration), + ["SetHudVisible"] = new Action(SetHeatHudVisible), + ["SetHudOffset"] = new Action(SetHeatHudOffset), + }); + } + + private void RegisterExistingGrids() + { + var entities = new HashSet(); + MyAPIGateway.Entities.GetEntities(entities, entity => entity is IMyCubeGrid && entity.Physics != null); + + foreach (var entity in entities) + EnsureGrid((IMyCubeGrid)entity); + } + private void OnEntityAdd(IMyEntity entity) { if (!(entity is IMyCubeGrid) || entity.Physics == null) return; var grid = (IMyCubeGrid)entity; - _heatSystems[grid] = new GridHeatManager(grid); + EnsureGrid(grid); } private void OnEntityRemove(IMyEntity entity) @@ -76,18 +200,32 @@ private void OnEntityRemove(IMyEntity entity) return; var grid = (IMyCubeGrid)entity; + if (!_heatSystems.ContainsKey(grid)) + return; + _heatSystems[grid].Unload(); _heatSystems.Remove(grid); } public void OnPartAdd(int assemblyId, IMyCubeBlock block, bool isBaseBlock) { - _heatSystems[block.CubeGrid].OnPartAdd(assemblyId, block, isBaseBlock); + EnsureGrid(block.CubeGrid)?.OnPartAdd(assemblyId, block, isBaseBlock); } public void OnPartRemove(int assemblyId, IMyCubeBlock block, bool isBaseBlock) { - _heatSystems[block.CubeGrid].OnPartRemove(assemblyId, block, isBaseBlock); + EnsureGrid(block.CubeGrid)?.OnPartRemove(assemblyId, block, isBaseBlock); + } + + private GridHeatManager EnsureGrid(IMyCubeGrid grid) + { + if (grid == null) + return null; + + if (!_heatSystems.ContainsKey(grid)) + _heatSystems[grid] = new GridHeatManager(grid); + + return _heatSystems[grid]; } } -} \ No newline at end of file +} diff --git a/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HudHelpers/FusionWindow.cs b/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HudHelpers/FusionWindow.cs index e677e6414..129d368ca 100644 --- a/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HudHelpers/FusionWindow.cs +++ b/Utility Mods/MoA Fusion Systems/Data/Scripts/ModularAssemblies/HudHelpers/FusionWindow.cs @@ -20,26 +20,16 @@ internal class FusionWindow : CamSpaceNode private readonly GlyphFormat _stdTextFormat = new GlyphFormat(color: HudConstants.HudTextColor, alignment: TextAlignment.Center, font: FontManager.GetFont("Monospace")); private readonly GlyphFormat _stdTextFormatInfo = new GlyphFormat(color: HudConstants.HudTextColor, textSize: 0.6f, alignment: TextAlignment.Left, font: FontManager.GetFont("Monospace")); - private static readonly Vector3D UnitTransformOffset = new Vector3D(-0.759375, -0.8, 0); - - public FusionWindow(HudParentBase parent) : base(parent) - { - RotationAxis = new Vector3(0, 1, 0); - RotationAngle = 0.25f; - - //var invert = MatrixD.CreatePerspectiveFieldOfView(MathHelper.ToRadians(90), 16 / 9d, - // MyAPIGateway.Session.Camera.NearPlaneDistance, MyAPIGateway.Session.Camera.FarPlaneDistance); - //ModularApi.Log(Vector3D.Transform(new Vector3D(-0.0675, -0.04, -0.05), invert).ToString()); - - var fovMatrix = MatrixD.Invert(MatrixD.CreatePerspectiveFieldOfView( - MathHelper.ToRadians(MyAPIGateway.Session.Camera.FieldOfViewAngle), HudMain.AspectRatio, - MyAPIGateway.Session.Camera.NearPlaneDistance, MyAPIGateway.Session.Camera.FarPlaneDistance)); - - TransformOffset = Vector3D.Transform(UnitTransformOffset, fovMatrix); - - - _backgroundBox = new TexturedBox(this) - { + private static readonly Vector3D DefaultUnitTransformOffset = new Vector3D(-0.759375, -0.8, 0); + + public FusionWindow(HudParentBase parent) : base(parent) + { + RotationAxis = new Vector3(0, 1, 0); + RotationAngle = 0.25f; + UpdateTransformOffset(); + + _backgroundBox = new TexturedBox(this) + { Material = new Material("WhiteSquare", HudConstants.HudSize), Size = HudConstants.HudSize, Color = HudConstants.HudBackgroundColor, @@ -105,19 +95,20 @@ public FusionWindow(HudParentBase parent) : base(parent) private MyEntity3DSoundEmitter _soundEmitter = null; private readonly MySoundPair _alertSound = new MySoundPair("ArcSoundBlockAlert2"); - public void Update() - { - _ticks++; - var playerCockpit = MyAPIGateway.Session?.Player?.Controller?.ControlledEntity?.Entity as IMyShipController; + public void Update() + { + _ticks++; + UpdateTransformOffset(); + var playerCockpit = MyAPIGateway.Session?.Player?.Controller?.ControlledEntity?.Entity as IMyShipController; // Pulling the current HudState is SLOOOOWWWW, so we only pull it when tab is just pressed. //if (MyAPIGateway.Input.IsNewKeyPressed(MyKeys.Tab)) // _shouldHide = MyAPIGateway.Session?.Config?.HudState != 1; // Hide HUD element if the player isn't in a cockpit - if (playerCockpit == null || _shouldHide) - { - if (Visible) Visible = false; + if (playerCockpit == null || _shouldHide || !HeatManager.I.HeatHudVisible) + { + if (Visible) Visible = false; if (_soundEmitter != null) { @@ -154,13 +145,15 @@ public void Update() reactorCount++; } } - reactorIntegrity /= reactorCount; - - // Hide HUD element if the grid has no fusion systems (capacity is always >0 for a fusion system) - if (totalFusionCapacity == 0) - { - if (Visible) Visible = false; - return; + reactorIntegrity = reactorCount > 0 ? reactorIntegrity / reactorCount : 1; + var hasFusion = totalFusionCapacity > 0; + var hasApiHeat = HeatManager.I.HasGridHeat(playerGrid); + + // Hide HUD element if the grid has no fusion systems and no API-managed heat. + if (!hasFusion && !hasApiHeat) + { + if (Visible) Visible = false; + return; } // Show otherwise @@ -171,19 +164,32 @@ public void Update() _heatBox.Width = 384 * HudConstants.HudSizeRatio.X * heatPct; _heatBox.Color = new Color(heatPct, 1-heatPct, 0, 0.75f); - _storBox.Width = 384 * HudConstants.HudSizeRatio.X * (totalFusionStored / totalFusionCapacity); - - _infoLabelLeft.Text = new RichText - { - {(reactorIntegrity*100).ToString("N0") + "%", _stdTextFormatInfo.WithColor(reactorIntegrity > 0.6 ? Color.White : Color.Red)}, - {" INTEGRITY - ", _stdTextFormatInfo}, - {GetNoticeText(heatPct, reactorIntegrity), GetNoticeFormat(heatPct, reactorIntegrity)}, - }; - - _heatLabel.Text = $"{heatPct*100:N0}% HEAT"; - _storageLabel.Text = $"{(totalFusionStored / totalFusionCapacity) * 100:N0}% STOR"; - - if (heatPct > 0.8f) + if (hasFusion) + { + _storBox.Width = 384 * HudConstants.HudSizeRatio.X * (totalFusionStored / totalFusionCapacity); + + _infoLabelLeft.Text = new RichText + { + {(reactorIntegrity*100).ToString("N0") + "%", _stdTextFormatInfo.WithColor(reactorIntegrity > 0.6 ? Color.White : Color.Red)}, + {" INTEGRITY - ", _stdTextFormatInfo}, + {GetNoticeText(heatPct, reactorIntegrity), GetNoticeFormat(heatPct, reactorIntegrity)}, + }; + + _storageLabel.Text = $"{(totalFusionStored / totalFusionCapacity) * 100:N0}% STOR"; + } + else + { + _storBox.Width = 0; + _infoLabelLeft.Text = new RichText + { + {"GRID HEAT API", _stdTextFormatInfo.WithColor(heatPct > 0.8f ? Color.Red : Color.White)}, + }; + _storageLabel.Text = "GRID HEAT"; + } + + _heatLabel.Text = $"{heatPct*100:N0}% HEAT"; + + if (heatPct > 0.8f) { if (_soundEmitter == null) { @@ -201,10 +207,20 @@ public void Update() _soundEmitter.StopSound(true); _soundEmitter = null; } - } - } - - private int _errRemainingTicks = 0; + } + } + + private void UpdateTransformOffset() + { + var fovMatrix = MatrixD.Invert(MatrixD.CreatePerspectiveFieldOfView( + MathHelper.ToRadians(MyAPIGateway.Session.Camera.FieldOfViewAngle), HudMain.AspectRatio, + MyAPIGateway.Session.Camera.NearPlaneDistance, MyAPIGateway.Session.Camera.FarPlaneDistance)); + + var offset = HeatManager.I?.HeatHudOffset ?? DefaultUnitTransformOffset; + TransformOffset = Vector3D.Transform(offset, fovMatrix); + } + + private int _errRemainingTicks = 0; private float _lastIntegrityPct = 1; private string _lastErrText = ""; private string GetNoticeText(float heatPct, float integrityPct) diff --git a/Utility Mods/MoA Fusion Systems/GridHeatAPI.md b/Utility Mods/MoA Fusion Systems/GridHeatAPI.md new file mode 100644 index 000000000..3a50ffbcc --- /dev/null +++ b/Utility Mods/MoA Fusion Systems/GridHeatAPI.md @@ -0,0 +1,45 @@ +# Grid Heat API + +`GridHeatApi.cs` exposes the fusion heat system as a small grid-level API other mods can call without knowing anything about fusion assemblies. + +## Quick Use + +Copy `Data/Scripts/ModularAssemblies/HeatParts/GridHeatApi.cs` into your mod and keep one instance on your session component: + +```csharp +private readonly GridHeatApi _heat = new GridHeatApi(); + +public override void LoadData() +{ + _heat.Load(); +} + +protected override void UnloadData() +{ + _heat.Unload(); +} +``` + +## Common Calls + +```csharp +_heat.SetHeatCapacity(grid, 1000f); +_heat.SetHeatDissipation(grid, 20f); +_heat.AddHeat(grid, 75f); + +float heat = _heat.GetHeat(grid); +float heatRatio = _heat.GetHeatRatio(grid); +``` + +`SetHeatCapacity` and `SetHeatDissipation` add API-owned capacity/dissipation on top of the heat blocks already provided by Fusion Systems. `AddHeat` and `SetHeat` are clamped to the grid's total heat capacity. + +## HUD Position + +The HUD heat bar uses the same camera-space offset as the Fusion Systems HUD. Other mods can hide or move it: + +```csharp +_heat.SetHudVisible(true); +_heat.SetHudOffset(-0.76f, -0.80f, 0f); +``` + +The offset values are camera-space X/Y/Z values. The default is the existing Fusion Systems position.