SharedSingularitySystem.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. using System.Numerics;
  2. using Content.Shared.Radiation.Components;
  3. using Content.Shared.Singularity.Components;
  4. using Content.Shared.Singularity.Events;
  5. using Robust.Shared.Containers;
  6. using Robust.Shared.Physics.Components;
  7. using Robust.Shared.Physics.Systems;
  8. using Robust.Shared.Serialization;
  9. namespace Content.Shared.Singularity.EntitySystems;
  10. /// <summary>
  11. /// The entity system primarily responsible for managing <see cref="SingularityComponent"/>s.
  12. /// </summary>
  13. public abstract class SharedSingularitySystem : EntitySystem
  14. {
  15. #region Dependencies
  16. [Dependency] private readonly SharedAppearanceSystem _visualizer = default!;
  17. [Dependency] private readonly SharedContainerSystem _containers = default!;
  18. [Dependency] private readonly SharedEventHorizonSystem _horizons = default!;
  19. [Dependency] private readonly SharedPhysicsSystem _physics = default!;
  20. [Dependency] protected readonly IViewVariablesManager Vvm = default!;
  21. #endregion Dependencies
  22. /// <summary>
  23. /// The minimum level a singularity can be set to.
  24. /// </summary>
  25. public const byte MinSingularityLevel = 0;
  26. /// <summary>
  27. /// The maximum level a singularity can be set to.
  28. /// </summary>
  29. public const byte MaxSingularityLevel = 6;
  30. /// <summary>
  31. /// The amount to scale a singularities distortion shader by when it's in a container.
  32. /// This is the inverse of an exponent, not a linear scaling factor.
  33. /// ie. n => intensity = intensity ** (1/n)
  34. /// </summary>
  35. public const float DistortionContainerScaling = 4f;
  36. public override void Initialize()
  37. {
  38. base.Initialize();
  39. SubscribeLocalEvent<SingularityComponent, ComponentStartup>(OnSingularityStartup);
  40. SubscribeLocalEvent<AppearanceComponent, SingularityLevelChangedEvent>(UpdateAppearance);
  41. SubscribeLocalEvent<RadiationSourceComponent, SingularityLevelChangedEvent>(UpdateRadiation);
  42. SubscribeLocalEvent<PhysicsComponent, SingularityLevelChangedEvent>(UpdateBody);
  43. SubscribeLocalEvent<EventHorizonComponent, SingularityLevelChangedEvent>(UpdateEventHorizon);
  44. SubscribeLocalEvent<SingularityDistortionComponent, SingularityLevelChangedEvent>(UpdateDistortion);
  45. SubscribeLocalEvent<SingularityDistortionComponent, EntGotInsertedIntoContainerMessage>(UpdateDistortion);
  46. SubscribeLocalEvent<SingularityDistortionComponent, EntGotRemovedFromContainerMessage>(UpdateDistortion);
  47. var vvHandle = Vvm.GetTypeHandler<SingularityComponent>();
  48. vvHandle.AddPath(nameof(SingularityComponent.Level), (_, comp) => comp.Level, SetLevel);
  49. vvHandle.AddPath(nameof(SingularityComponent.RadsPerLevel), (_, comp) => comp.RadsPerLevel, SetRadsPerLevel);
  50. }
  51. public override void Shutdown()
  52. {
  53. var vvHandle = Vvm.GetTypeHandler<SingularityComponent>();
  54. vvHandle.RemovePath(nameof(SingularityComponent.Level));
  55. vvHandle.RemovePath(nameof(SingularityComponent.RadsPerLevel));
  56. base.Shutdown();
  57. }
  58. #region Getters/Setters
  59. /// <summary>
  60. /// Setter for <see cref="SingularityComponent.Level"/>
  61. /// Also sends out an event alerting that the singularities level has changed.
  62. /// </summary>
  63. /// <param name="uid">The uid of the singularity to change the level of.</param>
  64. /// <param name="value">The new level the singularity should have.</param>
  65. /// <param name="singularity">The state of the singularity to change the level of.</param>
  66. public void SetLevel(EntityUid uid, byte value, SingularityComponent? singularity = null)
  67. {
  68. if(!Resolve(uid, ref singularity))
  69. return;
  70. value = MathHelper.Clamp(value, MinSingularityLevel, MaxSingularityLevel);
  71. var oldValue = singularity.Level;
  72. if (oldValue == value)
  73. return;
  74. singularity.Level = value;
  75. UpdateSingularityLevel(uid, oldValue, singularity);
  76. if (!Deleted(uid))
  77. Dirty(uid, singularity);
  78. }
  79. /// <summary>
  80. /// Setter for <see cref="SingularityComponent.RadsPerLevel"/>
  81. /// Also updates the radiation output of the singularity according to the new values.
  82. /// </summary>
  83. /// <param name="uid">The uid of the singularity to change the radioactivity of.</param>
  84. /// <param name="value">The new radioactivity the singularity should have.</param>
  85. /// <param name="singularity">The state of the singularity to change the radioactivity of.</param>
  86. public void SetRadsPerLevel(EntityUid uid, float value, SingularityComponent? singularity = null)
  87. {
  88. if(!Resolve(uid, ref singularity))
  89. return;
  90. var oldValue = singularity.RadsPerLevel;
  91. if (oldValue == value)
  92. return;
  93. singularity.RadsPerLevel = value;
  94. UpdateRadiation(uid, singularity);
  95. }
  96. /// <summary>
  97. /// Alerts the entity hosting the singularity that the level of the singularity has changed.
  98. /// Usually follows a SharedSingularitySystem.SetLevel call, but is also used on component startup to sync everything.
  99. /// </summary>
  100. /// <param name="uid">The uid of the singularity which's level has changed.</param>
  101. /// <param name="oldValue">The old level of the singularity. May be equal to <see cref="SingularityComponent.Level"/> if the component is starting.</param>
  102. /// <param name="singularity">The state of the singularity which's level has changed.</param>
  103. public void UpdateSingularityLevel(EntityUid uid, byte oldValue, SingularityComponent? singularity = null)
  104. {
  105. if(!Resolve(uid, ref singularity))
  106. return;
  107. RaiseLocalEvent(uid, new SingularityLevelChangedEvent(singularity.Level, oldValue, singularity));
  108. if (singularity.Level <= 0)
  109. QueueDel(uid);
  110. }
  111. /// <summary>
  112. /// Alerts the entity hosting the singularity that the level of the singularity has changed without the level actually changing.
  113. /// Used to sync components when the singularity component is added to an entity.
  114. /// </summary>
  115. /// <param name="uid">The uid of the singularity.</param>
  116. /// <param name="singularity">The state of the singularity.</param>
  117. public void UpdateSingularityLevel(EntityUid uid, SingularityComponent? singularity = null)
  118. {
  119. if (Resolve(uid, ref singularity))
  120. UpdateSingularityLevel(uid, singularity.Level, singularity);
  121. }
  122. /// <summary>
  123. /// Updates the amount of radiation the singularity emits to reflect a change in the level or radioactivity per level of the singularity.
  124. /// </summary>
  125. /// <param name="uid">The uid of the singularity to update the radiation of.</param>
  126. /// <param name="singularity">The state of the singularity to update the radiation of.</param>
  127. /// <param name="rads">The state of the radioactivity of the singularity to update.</param>
  128. private void UpdateRadiation(EntityUid uid, SingularityComponent? singularity = null, RadiationSourceComponent? rads = null)
  129. {
  130. if(!Resolve(uid, ref singularity, ref rads, logMissing: false))
  131. return;
  132. rads.Intensity = singularity.Level * singularity.RadsPerLevel;
  133. }
  134. #endregion Getters/Setters
  135. #region Derivations
  136. /// <summary>
  137. /// The scaling factor for the size of a singularities gravity well.
  138. /// </summary>
  139. public const float BaseGravityWellRadius = 2f;
  140. /// <summary>
  141. /// The scaling factor for the base acceleration of a singularities gravity well.
  142. /// </summary>
  143. public const float BaseGravityWellAcceleration = 10f;
  144. /// <summary>
  145. /// The level at and above which a singularity should be capable of breaching containment.
  146. /// </summary>
  147. public const byte SingularityBreachThreshold = 5;
  148. /// <summary>
  149. /// Derives the proper gravity well radius for a singularity from its state.
  150. /// </summary>
  151. /// <param name="singulo">A singularity.</param>
  152. /// <returns>The gravity well radius the singularity should have given its state.</returns>
  153. public float GravPulseRange(SingularityComponent singulo)
  154. => BaseGravityWellRadius * (singulo.Level + 1);
  155. /// <summary>
  156. /// Derives the proper base gravitational acceleration for a singularity from its state.
  157. /// </summary>
  158. /// <param name="singulo">A singularity.</param>
  159. /// <returns>The base gravitational acceleration the singularity should have given its state.</returns>
  160. public (float, float) GravPulseAcceleration(SingularityComponent singulo)
  161. => (BaseGravityWellAcceleration * singulo.Level, 0f);
  162. /// <summary>
  163. /// Derives the proper event horizon radius for a singularity from its state.
  164. /// </summary>
  165. /// <param name="singulo">A singularity.</param>
  166. /// <returns>The event horizon radius the singularity should have given its state.</returns>
  167. public float EventHorizonRadius(SingularityComponent singulo)
  168. => singulo.Level - 0.5f;
  169. /// <summary>
  170. /// Derives whether a singularity should be able to breach containment from its state.
  171. /// </summary>
  172. /// <param name="singulo">A singularity.</param>
  173. /// <returns>Whether the singularity should be able to breach containment.</returns>
  174. public bool CanBreachContainment(SingularityComponent singulo)
  175. => singulo.Level >= SingularityBreachThreshold;
  176. /// <summary>
  177. /// Derives the proper distortion shader falloff for a singularity from its state.
  178. /// </summary>
  179. /// <param name="singulo">A singularity.</param>
  180. /// <returns>The distortion shader falloff the singularity should have given its state.</returns>
  181. public float GetFalloff(float level)
  182. {
  183. return level switch {
  184. 0 => 9999f,
  185. 1 => MathF.Sqrt(6.4f),
  186. 2 => MathF.Sqrt(7.0f),
  187. 3 => MathF.Sqrt(8.0f),
  188. 4 => MathF.Sqrt(10.0f),
  189. 5 => MathF.Sqrt(12.0f),
  190. 6 => MathF.Sqrt(12.0f),
  191. _ => -1.0f
  192. };
  193. }
  194. /// <summary>
  195. /// Derives the proper distortion shader intensity for a singularity from its state.
  196. /// </summary>
  197. /// <param name="singulo">A singularity.</param>
  198. /// <returns>The distortion shader intensity the singularity should have given its state.</returns>
  199. public float GetIntensity(float level)
  200. {
  201. return level switch {
  202. 0 => 0.0f,
  203. 1 => 3645f,
  204. 2 => 103680f,
  205. 3 => 1113920f,
  206. 4 => 16200000f,
  207. 5 => 180000000f,
  208. 6 => 180000000f,
  209. _ => -1.0f
  210. };
  211. }
  212. #endregion Derivations
  213. #region Serialization
  214. /// <summary>
  215. /// A state wrapper used to sync the singularity between the server and client.
  216. /// </summary>
  217. [Serializable, NetSerializable]
  218. protected sealed class SingularityComponentState : ComponentState
  219. {
  220. /// <summary>
  221. /// The level of the singularity to sync.
  222. /// </summary>
  223. public readonly byte Level;
  224. public SingularityComponentState(SingularityComponent singulo)
  225. {
  226. Level = singulo.Level;
  227. }
  228. }
  229. #endregion Serialization
  230. #region EventHandlers
  231. /// <summary>
  232. /// Syncs other components with the state of the singularity via event on startup.
  233. /// </summary>
  234. /// <param name="uid">The entity that is becoming a singularity.</param>
  235. /// <param name="comp">The singularity component that is being added to the entity.</param>
  236. /// <param name="args">The event arguments.</param>
  237. protected virtual void OnSingularityStartup(EntityUid uid, SingularityComponent comp, ComponentStartup args)
  238. {
  239. UpdateSingularityLevel(uid, comp);
  240. }
  241. // TODO: Figure out which systems should have control of which coupling.
  242. /// <summary>
  243. /// Syncs the radius of an event horizon associated with a singularity that just changed levels.
  244. /// </summary>
  245. /// <param name="uid">The entity that the event horizon and singularity are attached to.</param>
  246. /// <param name="comp">The event horizon associated with the singularity.</param>
  247. /// <param name="args">The event arguments.</param>
  248. private void UpdateEventHorizon(EntityUid uid, EventHorizonComponent comp, SingularityLevelChangedEvent args)
  249. {
  250. var singulo = args.Singularity;
  251. _horizons.SetRadius(uid, EventHorizonRadius(singulo), false, comp);
  252. _horizons.SetCanBreachContainment(uid, CanBreachContainment(singulo), false, comp);
  253. _horizons.UpdateEventHorizonFixture(uid, eventHorizon: comp);
  254. }
  255. /// <summary>
  256. /// Updates the distortion shader associated with a singularity when the singuarity changes levels.
  257. /// </summary>
  258. /// <param name="uid">The uid of the distortion shader.</param>
  259. /// <param name="comp">The state of the distortion shader.</param>
  260. /// <param name="args">The event arguments.</param>
  261. private void UpdateDistortion(EntityUid uid, SingularityDistortionComponent comp, SingularityLevelChangedEvent args)
  262. {
  263. var newFalloffPower = GetFalloff(args.NewValue);
  264. var newIntensity = GetIntensity(args.NewValue);
  265. if (_containers.IsEntityInContainer(uid))
  266. {
  267. var absFalloffPower = MathF.Abs(newFalloffPower);
  268. var absIntensity = MathF.Abs(newIntensity);
  269. var factor = (1f / DistortionContainerScaling) - 1f;
  270. newFalloffPower = absFalloffPower > 1f ? newFalloffPower * MathF.Pow(absFalloffPower, factor) : newFalloffPower;
  271. newIntensity = absIntensity > 1f ? newIntensity * MathF.Pow(absIntensity, factor) : newIntensity;
  272. }
  273. comp.FalloffPower = newFalloffPower;
  274. comp.Intensity = newIntensity;
  275. Dirty(uid, comp);
  276. }
  277. /// <summary>
  278. /// Updates the distortion shader associated with a singularity when the singuarity is inserted into a container.
  279. /// </summary>
  280. /// <param name="uid">The uid of the distortion shader.</param>
  281. /// <param name="comp">The state of the distortion shader.</param>
  282. /// <param name="args">The event arguments.</param>
  283. private void UpdateDistortion(EntityUid uid, SingularityDistortionComponent comp, EntGotInsertedIntoContainerMessage args)
  284. {
  285. var absFalloffPower = MathF.Abs(comp.FalloffPower);
  286. var absIntensity = MathF.Abs(comp.Intensity);
  287. var factor = (1f / DistortionContainerScaling) - 1f;
  288. comp.FalloffPower = absFalloffPower > 1 ? comp.FalloffPower * MathF.Pow(absFalloffPower, factor) : comp.FalloffPower;
  289. comp.Intensity = absIntensity > 1 ? comp.Intensity * MathF.Pow(absIntensity, factor) : comp.Intensity;
  290. }
  291. /// <summary>
  292. /// Updates the distortion shader associated with a singularity when the singuarity is removed from a container.
  293. /// </summary>
  294. /// <param name="uid">The uid of the distortion shader.</param>
  295. /// <param name="comp">The state of the distortion shader.</param>
  296. /// <param name="args">The event arguments.</param>
  297. private void UpdateDistortion(EntityUid uid, SingularityDistortionComponent comp, EntGotRemovedFromContainerMessage args)
  298. {
  299. var absFalloffPower = MathF.Abs(comp.FalloffPower);
  300. var absIntensity = MathF.Abs(comp.Intensity);
  301. var factor = DistortionContainerScaling - 1;
  302. comp.FalloffPower = absFalloffPower > 1 ? comp.FalloffPower * MathF.Pow(absFalloffPower, factor) : comp.FalloffPower;
  303. comp.Intensity = absIntensity > 1 ? comp.Intensity * MathF.Pow(absIntensity, factor) : comp.Intensity;
  304. }
  305. /// <summary>
  306. /// Updates the state of the physics body associated with a singularity when the singualrity changes levels.
  307. /// </summary>
  308. /// <param name="uid">The entity that the physics body and singularity are attached to.</param>
  309. /// <param name="comp">The physics body associated with the singularity.</param>
  310. /// <param name="args">The event arguments.</param>
  311. private void UpdateBody(EntityUid uid, PhysicsComponent comp, SingularityLevelChangedEvent args)
  312. {
  313. if (args.NewValue <= 1 && args.OldValue > 1) // Apparently keeps singularities from getting stuck in the corners of containment fields.
  314. _physics.SetLinearVelocity(uid, Vector2.Zero, body: comp); // No idea how stopping the singularities movement keeps it from getting stuck though.
  315. }
  316. /// <summary>
  317. /// Updates the appearance of a singularity when the singularities level changes.
  318. /// </summary>
  319. /// <param name="uid">The entity that the singularity is attached to.</param>
  320. /// <param name="comp">The appearance associated with the singularity.</param>
  321. /// <param name="args">The event arguments.</param>
  322. private void UpdateAppearance(EntityUid uid, AppearanceComponent comp, SingularityLevelChangedEvent args)
  323. {
  324. _visualizer.SetData(uid, SingularityAppearanceKeys.Singularity, args.NewValue, comp);
  325. }
  326. /// <summary>
  327. /// Updates the amount of radiation a singularity emits when the singularities level changes.
  328. /// </summary>
  329. /// <param name="uid">The entity that the singularity is attached to.</param>
  330. /// <param name="comp">The radiation source associated with the singularity.</param>
  331. /// <param name="args">The event arguments.</param>
  332. private void UpdateRadiation(EntityUid uid, RadiationSourceComponent comp, SingularityLevelChangedEvent args)
  333. {
  334. UpdateRadiation(uid, args.Singularity, comp);
  335. }
  336. #endregion EventHandlers
  337. }