SingularitySystem.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. using Content.Server.Physics.Components;
  2. using Content.Server.Singularity.Components;
  3. using Content.Server.Singularity.Events;
  4. using Content.Shared.Singularity.Components;
  5. using Content.Shared.Singularity.EntitySystems;
  6. using Content.Shared.Singularity.Events;
  7. using Robust.Server.GameStates;
  8. using Robust.Shared.Audio;
  9. using Robust.Shared.Audio.Systems;
  10. using Robust.Shared.GameStates;
  11. using Robust.Shared.Player;
  12. using Robust.Shared.Timing;
  13. namespace Content.Server.Singularity.EntitySystems;
  14. /// <summary>
  15. /// The server-side version of <see cref="SharedSingularitySystem"/>.
  16. /// Primarily responsible for managing <see cref="SingularityComponent"/>s.
  17. /// Handles their accumulation of energy upon consuming entities (see <see cref="EventHorizonComponent"/>) and gradual dissipation.
  18. /// Also handles synchronizing server-side components with the singuarities level.
  19. /// </summary>
  20. public sealed class SingularitySystem : SharedSingularitySystem
  21. {
  22. #region Dependencies
  23. [Dependency] private readonly IGameTiming _timing = default!;
  24. [Dependency] private readonly SharedAudioSystem _audio = default!;
  25. [Dependency] private readonly PvsOverrideSystem _pvs = default!;
  26. #endregion Dependencies
  27. /// <summary>
  28. /// The amount of energy singulos accumulate when they eat a tile.
  29. /// </summary>
  30. public const float BaseTileEnergy = 1f;
  31. /// <summary>
  32. /// The amount of energy singulos accumulate when they eat an entity.
  33. /// </summary>
  34. public const float BaseEntityEnergy = 1f;
  35. public override void Initialize()
  36. {
  37. base.Initialize();
  38. SubscribeLocalEvent<SingularityDistortionComponent, ComponentStartup>(OnDistortionStartup);
  39. SubscribeLocalEvent<SingularityComponent, ComponentShutdown>(OnSingularityShutdown);
  40. SubscribeLocalEvent<SingularityComponent, EventHorizonConsumedEntityEvent>(OnConsumed);
  41. SubscribeLocalEvent<SinguloFoodComponent, EventHorizonConsumedEntityEvent>(OnConsumed);
  42. SubscribeLocalEvent<SingularityComponent, EntityConsumedByEventHorizonEvent>(OnConsumedEntity);
  43. SubscribeLocalEvent<SingularityComponent, TilesConsumedByEventHorizonEvent>(OnConsumedTiles);
  44. SubscribeLocalEvent<SingularityComponent, SingularityLevelChangedEvent>(UpdateEnergyDrain);
  45. SubscribeLocalEvent<SingularityComponent, ComponentGetState>(HandleSingularityState);
  46. // TODO: Figure out where all this coupling should be handled.
  47. SubscribeLocalEvent<RandomWalkComponent, SingularityLevelChangedEvent>(UpdateRandomWalk);
  48. SubscribeLocalEvent<GravityWellComponent, SingularityLevelChangedEvent>(UpdateGravityWell);
  49. var vvHandle = Vvm.GetTypeHandler<SingularityComponent>();
  50. vvHandle.AddPath(nameof(SingularityComponent.Energy), (_, comp) => comp.Energy, SetEnergy);
  51. }
  52. public override void Shutdown()
  53. {
  54. var vvHandle = Vvm.GetTypeHandler<SingularityComponent>();
  55. vvHandle.RemovePath(nameof(SingularityComponent.Energy));
  56. base.Shutdown();
  57. }
  58. /// <summary>
  59. /// Handles the gradual dissipation of all singularities.
  60. /// </summary>
  61. /// <param name="frameTime">The amount of time since the last set of updates.</param>
  62. public override void Update(float frameTime)
  63. {
  64. if(!_timing.IsFirstTimePredicted)
  65. return;
  66. var query = EntityQueryEnumerator<SingularityComponent>();
  67. while (query.MoveNext(out var uid, out var singularity))
  68. {
  69. AdjustEnergy(uid, -singularity.EnergyDrain * frameTime, singularity: singularity);
  70. }
  71. }
  72. #region Getters/Setters
  73. /// <summary>
  74. /// Setter for <see cref="SingularityComponent.Energy"/>.
  75. /// Also updates the level of the singularity accordingly.
  76. /// </summary>
  77. /// <param name="uid">The uid of the singularity to set the energy of.</param>
  78. /// <param name="value">The amount of energy for the singularity to have.</param>
  79. /// <param name="singularity">The state of the singularity to set the energy of.</param>
  80. public void SetEnergy(EntityUid uid, float value, SingularityComponent? singularity = null)
  81. {
  82. if(!Resolve(uid, ref singularity))
  83. return;
  84. var oldValue = singularity.Energy;
  85. if (oldValue == value)
  86. return;
  87. singularity.Energy = value;
  88. SetLevel(uid, value switch
  89. {
  90. // Normally, a level 6 singularity requires the supermatter + 3000 energy.
  91. // The required amount of energy has been bumped up to compensate for the lack of the supermatter.
  92. >= 5000 => 6,
  93. >= 2000 => 5,
  94. >= 1000 => 4,
  95. >= 500 => 3,
  96. >= 200 => 2,
  97. > 0 => 1,
  98. _ => 0
  99. }, singularity);
  100. }
  101. /// <summary>
  102. /// Adjusts the amount of energy the singularity has accumulated.
  103. /// </summary>
  104. /// <param name="uid">The uid of the singularity to adjust the energy of.</param>
  105. /// <param name="delta">The amount to adjust the energy of the singuarity.</param>
  106. /// <param name="min">The minimum amount of energy for the singularity to be adjusted to.</param>
  107. /// <param name="max">The maximum amount of energy for the singularity to be adjusted to.</param>
  108. /// <param name="snapMin">Whether the amount of energy in the singularity should be forced to within the specified range if it already is below it.</param>
  109. /// <param name="snapMax">Whether the amount of energy in the singularity should be forced to within the specified range if it already is above it.</param>
  110. /// <param name="singularity">The state of the singularity to adjust the energy of.</param>
  111. public void AdjustEnergy(EntityUid uid, float delta, float min = float.MinValue, float max = float.MaxValue, bool snapMin = true, bool snapMax = true, SingularityComponent? singularity = null)
  112. {
  113. if(!Resolve(uid, ref singularity))
  114. return;
  115. var newValue = singularity.Energy + delta;
  116. if((!snapMin && newValue < min)
  117. || (!snapMax && newValue > max))
  118. return;
  119. SetEnergy(uid, MathHelper.Clamp(newValue, min, max), singularity);
  120. }
  121. #endregion Getters/Setters
  122. #region Event Handlers
  123. /// <summary>
  124. /// Handles playing the startup sounds when a singulo forms.
  125. /// Always sets up the ambient singularity rumble.
  126. /// The formation sound only plays if the singularity is being created.
  127. /// </summary>
  128. /// <param name="uid">The entity UID of the singularity that is forming.</param>
  129. /// <param name="comp">The component of the singularity that is forming.</param>
  130. /// <param name="args">The event arguments.</param>
  131. protected override void OnSingularityStartup(EntityUid uid, SingularityComponent comp, ComponentStartup args)
  132. {
  133. MetaDataComponent? metaData = null;
  134. if (Resolve(uid, ref metaData) && metaData.EntityLifeStage <= EntityLifeStage.Initializing)
  135. _audio.PlayPvs(comp.FormationSound, uid);
  136. comp.AmbientSoundStream = _audio.PlayPvs(comp.AmbientSound, uid)?.Entity;
  137. UpdateSingularityLevel(uid, comp);
  138. }
  139. /// <summary>
  140. /// Makes entities that have the singularity distortion visual warping always get their state shared with the client.
  141. /// This prevents some major popin with large distortion ranges.
  142. /// </summary>
  143. /// <param name="uid">The entity UID of the entity that is gaining the shader.</param>
  144. /// <param name="comp">The component of the shader that the entity is gaining.</param>
  145. /// <param name="args">The event arguments.</param>
  146. public void OnDistortionStartup(EntityUid uid, SingularityDistortionComponent comp, ComponentStartup args)
  147. {
  148. _pvs.AddGlobalOverride(uid);
  149. }
  150. /// <summary>
  151. /// Handles playing the shutdown sounds when a singulo dissipates.
  152. /// Always stops the ambient singularity rumble.
  153. /// The dissipations sound only plays if the singularity is being destroyed.
  154. /// </summary>
  155. /// <param name="uid">The entity UID of the singularity that is dissipating.</param>
  156. /// <param name="comp">The component of the singularity that is dissipating.</param>
  157. /// <param name="args">The event arguments.</param>
  158. public void OnSingularityShutdown(EntityUid uid, SingularityComponent comp, ComponentShutdown args)
  159. {
  160. comp.AmbientSoundStream = _audio.Stop(comp.AmbientSoundStream);
  161. MetaDataComponent? metaData = null;
  162. if (Resolve(uid, ref metaData) && metaData.EntityLifeStage >= EntityLifeStage.Terminating)
  163. {
  164. var xform = Transform(uid);
  165. var coordinates = xform.Coordinates;
  166. // I feel like IsValid should be checking this or something idk.
  167. if (!TerminatingOrDeleted(coordinates.EntityId))
  168. _audio.PlayPvs(comp.DissipationSound, coordinates);
  169. }
  170. }
  171. /// <summary>
  172. /// Handles wrapping the state of a singularity for server-client syncing.
  173. /// </summary>
  174. /// <param name="uid">The uid of the singularity that is being synced.</param>
  175. /// <param name="comp">The state of the singularity that is being synced.</param>
  176. /// <param name="args">The event arguments.</param>
  177. private void HandleSingularityState(EntityUid uid, SingularityComponent comp, ref ComponentGetState args)
  178. {
  179. args.State = new SingularityComponentState(comp);
  180. }
  181. /// <summary>
  182. /// Adds the energy of any entities that are consumed to the singularity that consumed them.
  183. /// </summary>
  184. /// <param name="uid">The entity UID of the singularity that is consuming the entity.</param>
  185. /// <param name="comp">The component of the singularity that is consuming the entity.</param>
  186. /// <param name="args">The event arguments.</param>
  187. public void OnConsumedEntity(EntityUid uid, SingularityComponent comp, ref EntityConsumedByEventHorizonEvent args)
  188. {
  189. // Don't double count singulo food
  190. if (HasComp<SinguloFoodComponent>(args.Entity))
  191. return;
  192. AdjustEnergy(uid, BaseEntityEnergy, singularity: comp);
  193. }
  194. /// <summary>
  195. /// Adds the energy of any tiles that are consumed to the singularity that consumed them.
  196. /// </summary>
  197. /// <param name="uid">The entity UID of the singularity that is consuming the tiles.</param>
  198. /// <param name="comp">The component of the singularity that is consuming the tiles.</param>
  199. /// <param name="args">The event arguments.</param>
  200. public void OnConsumedTiles(EntityUid uid, SingularityComponent comp, ref TilesConsumedByEventHorizonEvent args)
  201. {
  202. AdjustEnergy(uid, args.Tiles.Count * BaseTileEnergy, singularity: comp);
  203. }
  204. /// <summary>
  205. /// Adds the energy of this singularity to singularities that consume it.
  206. /// </summary>
  207. /// <param name="uid">The entity UID of the singularity that is being consumed.</param>
  208. /// <param name="comp">The component of the singularity that is being consumed.</param>
  209. /// <param name="args">The event arguments.</param>
  210. private void OnConsumed(EntityUid uid, SingularityComponent comp, ref EventHorizonConsumedEntityEvent args)
  211. {
  212. // Should be slightly more efficient than checking literally everything we consume for a singularity component and doing the reverse.
  213. if (EntityManager.TryGetComponent<SingularityComponent>(args.EventHorizonUid, out var singulo))
  214. {
  215. AdjustEnergy(args.EventHorizonUid, comp.Energy, singularity: singulo);
  216. SetEnergy(uid, 0.0f, comp);
  217. }
  218. }
  219. /// <summary>
  220. /// Adds some bonus energy from any singularity food to the singularity that consumes it.
  221. /// </summary>
  222. /// <param name="uid">The entity UID of the singularity food that is being consumed.</param>
  223. /// <param name="comp">The component of the singularity food that is being consumed.</param>
  224. /// <param name="args">The event arguments.</param>
  225. public void OnConsumed(EntityUid uid, SinguloFoodComponent comp, ref EventHorizonConsumedEntityEvent args)
  226. {
  227. if (EntityManager.TryGetComponent<SingularityComponent>(args.EventHorizonUid, out var singulo))
  228. {
  229. // Calculate the percentage change (positive or negative)
  230. var percentageChange = singulo.Energy * (comp.EnergyFactor - 1f);
  231. // Apply both the flat and percentage changes
  232. AdjustEnergy(args.EventHorizonUid, comp.Energy + percentageChange, singularity: singulo);
  233. }
  234. }
  235. /// <summary>
  236. /// Updates the rate at which the singularities energy drains at when its level changes.
  237. /// </summary>
  238. /// <param name="uid">The entity UID of the singularity that changed in level.</param>
  239. /// <param name="comp">The component of the singularity that changed in level.</param>
  240. /// <param name="args">The event arguments.</param>
  241. public void UpdateEnergyDrain(EntityUid uid, SingularityComponent comp, SingularityLevelChangedEvent args)
  242. {
  243. comp.EnergyDrain = args.NewValue switch
  244. {
  245. 6 => 0,
  246. 5 => 0,
  247. 4 => 20,
  248. 3 => 10,
  249. 2 => 5,
  250. 1 => 1,
  251. _ => 0
  252. };
  253. }
  254. /// <summary>
  255. /// Updates the possible speeds of the singulos random walk when the singularities level changes.
  256. /// </summary>
  257. /// <param name="uid">The entity UID of the singularity.</param>
  258. /// <param name="comp">The random walk component component sharing the entity with the singulo component.</param>
  259. /// <param name="args">The event arguments.</param>
  260. private void UpdateRandomWalk(EntityUid uid, RandomWalkComponent comp, SingularityLevelChangedEvent args)
  261. {
  262. var scale = MathF.Max(args.NewValue, 4);
  263. comp.MinSpeed = 7.5f / scale;
  264. comp.MaxSpeed = 10f / scale;
  265. }
  266. /// <summary>
  267. /// Updates the size and strength of the singularities gravity well when the singularities level changes.
  268. /// </summary>
  269. /// <param name="uid">The entity UID of the singularity.</param>
  270. /// <param name="comp">The gravity well component sharing the entity with the singulo component.</param>
  271. /// <param name="args">The event arguments.</param>
  272. private void UpdateGravityWell(EntityUid uid, GravityWellComponent comp, SingularityLevelChangedEvent args)
  273. {
  274. var singulos = args.Singularity;
  275. comp.MaxRange = GravPulseRange(singulos);
  276. (comp.BaseRadialAcceleration, comp.BaseTangentialAcceleration) = GravPulseAcceleration(singulos);
  277. }
  278. #endregion Event Handlers
  279. }