ReagentProducerAnomalySystem.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. using Content.Server.Anomaly.Components;
  2. using Content.Shared.Chemistry.EntitySystems;
  3. using Content.Shared.Anomaly.Components;
  4. using Content.Shared.Chemistry.Components;
  5. using Content.Shared.Sprite;
  6. using Robust.Server.GameObjects;
  7. using Robust.Shared.Audio.Systems;
  8. using Robust.Shared.Prototypes;
  9. using Robust.Shared.Random;
  10. namespace Content.Server.Anomaly.Effects;
  11. /// <see cref="ReagentProducerAnomalyComponent"/>
  12. public sealed class ReagentProducerAnomalySystem : EntitySystem
  13. {
  14. //The idea is to divide substances into several categories.
  15. //The anomaly will choose one of the categories with a given chance based on severity.
  16. //Then a random substance will be selected from the selected category.
  17. //There are the following categories:
  18. //Dangerous:
  19. //selected most often. A list of substances that are extremely unpleasant for injection.
  20. //Fun:
  21. //Funny things have an increased chance of appearing in an anomaly.
  22. //Useful:
  23. //Those reagents that the players are hunting for. Very low percentage of loss.
  24. [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
  25. [Dependency] private readonly IRobustRandom _random = default!;
  26. [Dependency] private readonly PointLightSystem _light = default!;
  27. [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
  28. [Dependency] private readonly SharedAudioSystem _audio = default!;
  29. public const string FallbackReagent = "Water";
  30. public override void Initialize()
  31. {
  32. SubscribeLocalEvent<ReagentProducerAnomalyComponent, AnomalyPulseEvent>(OnPulse);
  33. SubscribeLocalEvent<ReagentProducerAnomalyComponent, MapInitEvent>(OnMapInit);
  34. }
  35. private void OnPulse(Entity<ReagentProducerAnomalyComponent> entity, ref AnomalyPulseEvent args)
  36. {
  37. if (_random.NextFloat(0.0f, 1.0f) > args.Stability)
  38. ChangeReagent(entity, args.Severity);
  39. }
  40. private void ChangeReagent(Entity<ReagentProducerAnomalyComponent> entity, float severity)
  41. {
  42. var reagent = GetRandomReagentType(entity, severity);
  43. entity.Comp.ProducingReagent = reagent;
  44. _audio.PlayPvs(entity.Comp.ChangeSound, entity);
  45. }
  46. //reagent realtime generation
  47. public override void Update(float frameTime)
  48. {
  49. base.Update(frameTime);
  50. var query = EntityQueryEnumerator<ReagentProducerAnomalyComponent, AnomalyComponent>();
  51. while (query.MoveNext(out var uid, out var component, out var anomaly))
  52. {
  53. component.AccumulatedFrametime += frameTime;
  54. if (component.AccumulatedFrametime < component.UpdateInterval)
  55. continue;
  56. if (!_solutionContainer.ResolveSolution(uid, component.SolutionName, ref component.Solution, out var producerSolution))
  57. continue;
  58. Solution newSol = new();
  59. var reagentProducingAmount = anomaly.Stability * component.MaxReagentProducing * component.AccumulatedFrametime;
  60. if (anomaly.Severity >= 0.97) reagentProducingAmount *= component.SupercriticalReagentProducingModifier;
  61. newSol.AddReagent(component.ProducingReagent, reagentProducingAmount);
  62. _solutionContainer.TryAddSolution(component.Solution.Value, newSol); // TODO - the container is not fully filled.
  63. component.AccumulatedFrametime = 0;
  64. // The component will repaint the sprites of the object to match the current color of the solution,
  65. // if the RandomSprite component is hung correctly.
  66. // Ideally, this should be put into a separate component, but I suffered for 4 hours,
  67. // and nothing worked out for me. So for now it will be like this.
  68. if (component.NeedRecolor)
  69. {
  70. var color = producerSolution.GetColor(_prototypeManager);
  71. _light.SetColor(uid, color);
  72. if (TryComp<RandomSpriteComponent>(uid, out var randomSprite))
  73. {
  74. foreach (var ent in randomSprite.Selected)
  75. {
  76. var state = randomSprite.Selected[ent.Key];
  77. state.Color = color;
  78. randomSprite.Selected[ent.Key] = state;
  79. }
  80. Dirty(uid, randomSprite);
  81. }
  82. }
  83. }
  84. }
  85. private void OnMapInit(Entity<ReagentProducerAnomalyComponent> entity, ref MapInitEvent args)
  86. {
  87. ChangeReagent(entity, 0.1f); //MapInit Reagent 100% change
  88. }
  89. // returns a random reagent based on a system of random weights.
  90. // First, the category is selected: The category has a minimum and maximum weight,
  91. // the current value depends on severity.
  92. // Accordingly, with the strengthening of the anomaly,
  93. // the chances of falling out of some categories grow, and some fall.
  94. //
  95. // After that, a random reagent in the selected category is selected.
  96. //
  97. // Such a system is made to control the danger and interest of the anomaly more.
  98. private string GetRandomReagentType(Entity<ReagentProducerAnomalyComponent> entity, float severity)
  99. {
  100. //Category Weight Randomization
  101. var currentWeightDangerous = MathHelper.Lerp(entity.Comp.WeightSpreadDangerous.X, entity.Comp.WeightSpreadDangerous.Y, severity);
  102. var currentWeightFun = MathHelper.Lerp(entity.Comp.WeightSpreadFun.X, entity.Comp.WeightSpreadFun.Y, severity);
  103. var currentWeightUseful = MathHelper.Lerp(entity.Comp.WeightSpreadUseful.X, entity.Comp.WeightSpreadUseful.Y, severity);
  104. var sumWeight = currentWeightDangerous + currentWeightFun + currentWeightUseful;
  105. var rnd = _random.NextFloat(0f, sumWeight);
  106. //Dangerous
  107. if (rnd <= currentWeightDangerous && entity.Comp.DangerousChemicals.Count > 0)
  108. {
  109. var reagent = _random.Pick(entity.Comp.DangerousChemicals);
  110. return reagent;
  111. }
  112. else rnd -= currentWeightDangerous;
  113. //Fun
  114. if (rnd <= currentWeightFun && entity.Comp.FunChemicals.Count > 0)
  115. {
  116. var reagent = _random.Pick(entity.Comp.FunChemicals);
  117. return reagent;
  118. }
  119. else rnd -= currentWeightFun;
  120. //Useful
  121. if (rnd <= currentWeightUseful && entity.Comp.UsefulChemicals.Count > 0)
  122. {
  123. var reagent = _random.Pick(entity.Comp.UsefulChemicals);
  124. return reagent;
  125. }
  126. //We should never end up here.
  127. //Maybe Log Error?
  128. return FallbackReagent;
  129. }
  130. }