1
0

AmeNodeGroup.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. using System.Linq;
  2. using Content.Server.Ame.Components;
  3. using Content.Server.Ame.EntitySystems;
  4. using Content.Server.Chat.Managers;
  5. using Content.Server.Explosion.EntitySystems;
  6. using Content.Server.NodeContainer.NodeGroups;
  7. using Content.Server.NodeContainer.Nodes;
  8. using Robust.Server.GameObjects;
  9. using Robust.Shared.Map.Components;
  10. using Robust.Shared.Random;
  11. namespace Content.Server.Ame;
  12. /// <summary>
  13. /// Node group class for handling the Antimatter Engine's console and parts.
  14. /// </summary>
  15. [NodeGroup(NodeGroupID.AMEngine)]
  16. public sealed class AmeNodeGroup : BaseNodeGroup
  17. {
  18. [Dependency] private readonly IChatManager _chat = default!;
  19. [Dependency] private readonly IEntityManager _entMan = default!;
  20. [Dependency] private readonly IRobustRandom _random = default!;
  21. /// <summary>
  22. /// The AME controller which is currently in control of this node group.
  23. /// This could be tracked a few different ways, but this is most convenient,
  24. /// since any part connected to the node group can easily find the master.
  25. /// </summary>
  26. [ViewVariables]
  27. private EntityUid? _masterController;
  28. public EntityUid? MasterController => _masterController;
  29. /// <summary>
  30. /// The set of AME shielding units that currently count as cores for the AME.
  31. /// </summary>
  32. private readonly List<EntityUid> _cores = new();
  33. public int CoreCount => _cores.Count;
  34. public override void LoadNodes(List<Node> groupNodes)
  35. {
  36. base.LoadNodes(groupNodes);
  37. EntityUid? gridEnt = null;
  38. var ameControllerSystem = _entMan.System<AmeControllerSystem>();
  39. var ameShieldingSystem = _entMan.System<AmeShieldingSystem>();
  40. var mapSystem = _entMan.System<MapSystem>();
  41. var shieldQuery = _entMan.GetEntityQuery<AmeShieldComponent>();
  42. var controllerQuery = _entMan.GetEntityQuery<AmeControllerComponent>();
  43. var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
  44. foreach (var node in groupNodes)
  45. {
  46. var nodeOwner = node.Owner;
  47. if (!shieldQuery.TryGetComponent(nodeOwner, out var shield))
  48. continue;
  49. if (!xformQuery.TryGetComponent(nodeOwner, out var xform))
  50. continue;
  51. if (!_entMan.TryGetComponent(xform.GridUid, out MapGridComponent? grid))
  52. continue;
  53. if (gridEnt == null)
  54. gridEnt = xform.GridUid;
  55. else if (gridEnt != xform.GridUid)
  56. continue;
  57. var nodeNeighbors = mapSystem.GetCellsInSquareArea(xform.GridUid.Value, grid, xform.Coordinates, 1)
  58. .Where(entity => entity != nodeOwner && shieldQuery.HasComponent(entity));
  59. if (nodeNeighbors.Count() >= 8)
  60. {
  61. _cores.Add(nodeOwner);
  62. ameShieldingSystem.SetCore(nodeOwner, true, shield);
  63. // Core visuals will be updated later.
  64. }
  65. else
  66. {
  67. ameShieldingSystem.SetCore(nodeOwner, false, shield);
  68. }
  69. }
  70. // Separate to ensure core count is correctly updated.
  71. foreach (var node in groupNodes)
  72. {
  73. var nodeOwner = node.Owner;
  74. if (!controllerQuery.TryGetComponent(nodeOwner, out var controller))
  75. continue;
  76. if (_masterController == null)
  77. _masterController = nodeOwner;
  78. ameControllerSystem.UpdateUi(nodeOwner, controller);
  79. }
  80. UpdateCoreVisuals();
  81. }
  82. public void UpdateCoreVisuals()
  83. {
  84. var injectionAmount = 0;
  85. var injecting = false;
  86. if (_entMan.TryGetComponent<AmeControllerComponent>(_masterController, out var controller))
  87. {
  88. injectionAmount = controller.InjectionAmount;
  89. injecting = controller.Injecting;
  90. }
  91. var injectionStrength = CoreCount > 0 ? injectionAmount / CoreCount : 0;
  92. var coreSystem = _entMan.System<AmeShieldingSystem>();
  93. foreach (var coreUid in _cores)
  94. {
  95. coreSystem.UpdateCoreVisuals(coreUid, injectionStrength, injecting);
  96. }
  97. }
  98. public float InjectFuel(int fuel, out bool overloading)
  99. {
  100. overloading = false;
  101. var shieldQuery = _entMan.GetEntityQuery<AmeShieldComponent>();
  102. if (fuel <= 0 || CoreCount <= 0)
  103. return 0;
  104. var safeFuelLimit = CoreCount * 2;
  105. var powerOutput = CalculatePower(fuel, CoreCount);
  106. if (fuel <= safeFuelLimit)
  107. return powerOutput;
  108. // The AME is being overloaded.
  109. // Note about these maths: I would assume the general idea here is to make larger engines less safe to overload.
  110. // In other words, yes, those are supposed to be CoreCount, not safeFuelLimit.
  111. var overloadVsSizeResult = fuel - CoreCount;
  112. var instability = overloadVsSizeResult / CoreCount;
  113. var fuzz = _random.Next(-1, 2); // -1 to 1
  114. instability += fuzz; // fuzz the values a tiny bit.
  115. overloading = true;
  116. var integrityCheck = 100;
  117. foreach (var coreUid in _cores)
  118. {
  119. if (!shieldQuery.TryGetComponent(coreUid, out var core))
  120. continue;
  121. var oldIntegrity = core.CoreIntegrity;
  122. core.CoreIntegrity -= instability;
  123. if (oldIntegrity > 95
  124. && core.CoreIntegrity <= 95
  125. && core.CoreIntegrity < integrityCheck)
  126. integrityCheck = core.CoreIntegrity;
  127. }
  128. // Admin alert
  129. if (integrityCheck != 100 && _masterController.HasValue)
  130. _chat.SendAdminAlert($"AME overloading: {_entMan.ToPrettyString(_masterController.Value)}");
  131. return powerOutput;
  132. }
  133. /// <summary>
  134. /// Calculates the amount of power the AME can produce with the given settings
  135. /// </summary>
  136. public float CalculatePower(int fuel, int cores)
  137. {
  138. // Balanced around a single core AME with injection level 2 producing 120KW.
  139. // Two core with four injection is 150kW. Two core with two injection is 90kW.
  140. // Increasing core count creates diminishing returns, increasing injection amount increases
  141. // Unlike the previous solution, increasing fuel and cores always leads to an increase in power, even if by very small amounts.
  142. // Increasing core count without increasing fuel always leads to reduced power as well.
  143. // At 18+ cores and 2 inject, the power produced is less than 0, the Max ensures the AME can never produce "negative" power.
  144. return MathF.Max(200000f * MathF.Log10(2 * fuel * MathF.Pow(cores, (float)-0.5)), 0);
  145. }
  146. public int GetTotalStability()
  147. {
  148. if (CoreCount < 1)
  149. return 100;
  150. var stability = 0;
  151. var coreQuery = _entMan.GetEntityQuery<AmeShieldComponent>();
  152. foreach (var coreUid in _cores)
  153. {
  154. if (coreQuery.TryGetComponent(coreUid, out var core))
  155. stability += core.CoreIntegrity;
  156. }
  157. stability /= CoreCount;
  158. return stability;
  159. }
  160. public void ExplodeCores()
  161. {
  162. if (_cores.Count < 1
  163. || !_entMan.TryGetComponent<AmeControllerComponent>(MasterController, out var controller))
  164. return;
  165. /*
  166. * todo: add an exact to the shielding and make this find the core closest to the controller
  167. * so they chain explode, after helpers have been added to make it not cancer
  168. */
  169. var radius = Math.Min(2 * CoreCount * controller.InjectionAmount, 8f);
  170. _entMan.System<ExplosionSystem>().TriggerExplosive(MasterController.Value, radius: radius, delete: false);
  171. }
  172. }