1
0

SharedStealthSystem.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. using Content.Shared.Examine;
  2. using Content.Shared.Mobs;
  3. using Content.Shared.Mobs.Systems;
  4. using Content.Shared.Stealth.Components;
  5. using Robust.Shared.GameStates;
  6. using Robust.Shared.Timing;
  7. namespace Content.Shared.Stealth;
  8. public abstract class SharedStealthSystem : EntitySystem
  9. {
  10. [Dependency] private readonly IGameTiming _timing = default!;
  11. public override void Initialize()
  12. {
  13. base.Initialize();
  14. SubscribeLocalEvent<StealthComponent, ComponentGetState>(OnStealthGetState);
  15. SubscribeLocalEvent<StealthComponent, ComponentHandleState>(OnStealthHandleState);
  16. SubscribeLocalEvent<StealthOnMoveComponent, MoveEvent>(OnMove);
  17. SubscribeLocalEvent<StealthOnMoveComponent, GetVisibilityModifiersEvent>(OnGetVisibilityModifiers);
  18. SubscribeLocalEvent<StealthComponent, EntityPausedEvent>(OnPaused);
  19. SubscribeLocalEvent<StealthComponent, EntityUnpausedEvent>(OnUnpaused);
  20. SubscribeLocalEvent<StealthComponent, ComponentInit>(OnInit);
  21. SubscribeLocalEvent<StealthComponent, ExamineAttemptEvent>(OnExamineAttempt);
  22. SubscribeLocalEvent<StealthComponent, ExaminedEvent>(OnExamined);
  23. SubscribeLocalEvent<StealthComponent, MobStateChangedEvent>(OnMobStateChanged);
  24. }
  25. private void OnExamineAttempt(EntityUid uid, StealthComponent component, ExamineAttemptEvent args)
  26. {
  27. if (!component.Enabled || GetVisibility(uid, component) > component.ExamineThreshold)
  28. return;
  29. // Don't block examine for owner or children of the cloaked entity.
  30. // Containers and the like should already block examining, so not bothering to check for occluding containers.
  31. var source = args.Examiner;
  32. do
  33. {
  34. if (source == uid)
  35. return;
  36. source = Transform(source).ParentUid;
  37. }
  38. while (source.IsValid());
  39. args.Cancel();
  40. }
  41. private void OnExamined(EntityUid uid, StealthComponent component, ExaminedEvent args)
  42. {
  43. if (component.Enabled)
  44. args.PushMarkup(Loc.GetString(component.ExaminedDesc, ("target", uid)));
  45. }
  46. public virtual void SetEnabled(EntityUid uid, bool value, StealthComponent? component = null)
  47. {
  48. if (!Resolve(uid, ref component, false) || component.Enabled == value)
  49. return;
  50. component.Enabled = value;
  51. Dirty(uid, component);
  52. }
  53. private void OnMobStateChanged(EntityUid uid, StealthComponent component, MobStateChangedEvent args)
  54. {
  55. if (args.NewMobState == MobState.Dead)
  56. {
  57. component.Enabled = component.EnabledOnDeath;
  58. }
  59. else
  60. {
  61. component.Enabled = true;
  62. }
  63. Dirty(uid, component);
  64. }
  65. private void OnPaused(EntityUid uid, StealthComponent component, ref EntityPausedEvent args)
  66. {
  67. component.LastVisibility = GetVisibility(uid, component);
  68. component.LastUpdated = null;
  69. Dirty(uid, component);
  70. }
  71. private void OnUnpaused(EntityUid uid, StealthComponent component, ref EntityUnpausedEvent args)
  72. {
  73. component.LastUpdated = _timing.CurTime;
  74. Dirty(uid, component);
  75. }
  76. protected virtual void OnInit(EntityUid uid, StealthComponent component, ComponentInit args)
  77. {
  78. if (component.LastUpdated != null || Paused(uid))
  79. return;
  80. component.LastUpdated = _timing.CurTime;
  81. }
  82. private void OnStealthGetState(EntityUid uid, StealthComponent component, ref ComponentGetState args)
  83. {
  84. args.State = new StealthComponentState(component.LastVisibility, component.LastUpdated, component.Enabled);
  85. }
  86. private void OnStealthHandleState(EntityUid uid, StealthComponent component, ref ComponentHandleState args)
  87. {
  88. if (args.Current is not StealthComponentState cast)
  89. return;
  90. SetEnabled(uid, cast.Enabled, component);
  91. component.LastVisibility = cast.Visibility;
  92. component.LastUpdated = cast.LastUpdated;
  93. }
  94. private void OnMove(EntityUid uid, StealthOnMoveComponent component, ref MoveEvent args)
  95. {
  96. if (_timing.ApplyingState)
  97. return;
  98. if (args.NewPosition.EntityId != args.OldPosition.EntityId)
  99. return;
  100. var delta = component.MovementVisibilityRate * (args.NewPosition.Position - args.OldPosition.Position).Length();
  101. ModifyVisibility(uid, delta);
  102. }
  103. private void OnGetVisibilityModifiers(EntityUid uid, StealthOnMoveComponent component, GetVisibilityModifiersEvent args)
  104. {
  105. var mod = args.SecondsSinceUpdate * component.PassiveVisibilityRate;
  106. args.FlatModifier += mod;
  107. }
  108. /// <summary>
  109. /// Modifies the visibility based on the delta provided.
  110. /// </summary>
  111. /// <param name="delta">The delta to be used in visibility calculation.</param>
  112. public void ModifyVisibility(EntityUid uid, float delta, StealthComponent? component = null)
  113. {
  114. if (delta == 0 || !Resolve(uid, ref component))
  115. return;
  116. if (component.LastUpdated != null)
  117. {
  118. component.LastVisibility = GetVisibility(uid, component);
  119. component.LastUpdated = _timing.CurTime;
  120. }
  121. component.LastVisibility = Math.Clamp(component.LastVisibility + delta, component.MinVisibility, component.MaxVisibility);
  122. Dirty(uid, component);
  123. }
  124. /// <summary>
  125. /// Sets the visibility directly with no modifications
  126. /// </summary>
  127. /// <param name="value">The value to set the visibility to. -1 is fully invisible, 1 is fully visible</param>
  128. public void SetVisibility(EntityUid uid, float value, StealthComponent? component = null)
  129. {
  130. if (!Resolve(uid, ref component))
  131. return;
  132. component.LastVisibility = Math.Clamp(value, component.MinVisibility, component.MaxVisibility);
  133. if (component.LastUpdated != null)
  134. component.LastUpdated = _timing.CurTime;
  135. Dirty(uid, component);
  136. }
  137. /// <summary>
  138. /// Gets the current visibility from the <see cref="StealthComponent"/>
  139. /// Use this instead of getting LastVisibility from the component directly.
  140. /// </summary>
  141. /// <returns>Returns a calculation that accounts for any stealth change that happened since last update, otherwise
  142. /// returns based on if it can resolve the component. Note that the returned value may be larger than the components
  143. /// maximum stealth value if it is currently disabled.</returns>
  144. public float GetVisibility(EntityUid uid, StealthComponent? component = null)
  145. {
  146. if (!Resolve(uid, ref component) || !component.Enabled)
  147. return 1;
  148. if (component.LastUpdated == null)
  149. return component.LastVisibility;
  150. var deltaTime = _timing.CurTime - component.LastUpdated.Value;
  151. var ev = new GetVisibilityModifiersEvent(uid, component, (float) deltaTime.TotalSeconds, 0f);
  152. RaiseLocalEvent(uid, ev, false);
  153. return Math.Clamp(component.LastVisibility + ev.FlatModifier, component.MinVisibility, component.MaxVisibility);
  154. }
  155. /// <summary>
  156. /// Used to run through any stealth effecting components on the entity.
  157. /// </summary>
  158. private sealed class GetVisibilityModifiersEvent : EntityEventArgs
  159. {
  160. public readonly StealthComponent Stealth;
  161. public readonly float SecondsSinceUpdate;
  162. /// <summary>
  163. /// Calculate this and add to it. Do not divide, multiply, or overwrite.
  164. /// The sum will be added to the stealth component's visibility.
  165. /// </summary>
  166. public float FlatModifier;
  167. public GetVisibilityModifiersEvent(EntityUid uid, StealthComponent stealth, float secondsSinceUpdate, float flatModifier)
  168. {
  169. Stealth = stealth;
  170. SecondsSinceUpdate = secondsSinceUpdate;
  171. FlatModifier = flatModifier;
  172. }
  173. }
  174. }