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.