RingerSystem.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. using System.Linq;
  2. using System.Runtime.InteropServices;
  3. using Content.Server.Store.Components;
  4. using Content.Server.Store.Systems;
  5. using Content.Shared.PDA;
  6. using Content.Shared.PDA.Ringer;
  7. using Content.Shared.Popups;
  8. using Content.Shared.Store;
  9. using Content.Shared.Store.Components;
  10. using Robust.Server.GameObjects;
  11. using Robust.Shared.Audio;
  12. using Robust.Shared.Network;
  13. using Robust.Shared.Player;
  14. using Robust.Shared.Random;
  15. using Robust.Shared.Timing;
  16. using Robust.Shared.Utility;
  17. using Robust.Server.Audio;
  18. namespace Content.Server.PDA.Ringer
  19. {
  20. public sealed class RingerSystem : SharedRingerSystem
  21. {
  22. [Dependency] private readonly PdaSystem _pda = default!;
  23. [Dependency] private readonly IGameTiming _gameTiming = default!;
  24. [Dependency] private readonly IRobustRandom _random = default!;
  25. [Dependency] private readonly UserInterfaceSystem _ui = default!;
  26. [Dependency] private readonly AudioSystem _audio = default!;
  27. [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
  28. [Dependency] private readonly TransformSystem _transform = default!;
  29. private readonly Dictionary<NetUserId, TimeSpan> _lastSetRingtoneAt = new();
  30. public override void Initialize()
  31. {
  32. base.Initialize();
  33. // General Event Subscriptions
  34. SubscribeLocalEvent<RingerComponent, MapInitEvent>(RandomizeRingtone);
  35. SubscribeLocalEvent<RingerUplinkComponent, ComponentInit>(RandomizeUplinkCode);
  36. // RingerBoundUserInterface Subscriptions
  37. SubscribeLocalEvent<RingerComponent, RingerSetRingtoneMessage>(OnSetRingtone);
  38. SubscribeLocalEvent<RingerUplinkComponent, BeforeRingtoneSetEvent>(OnSetUplinkRingtone);
  39. SubscribeLocalEvent<RingerComponent, RingerPlayRingtoneMessage>(RingerPlayRingtone);
  40. SubscribeLocalEvent<RingerComponent, RingerRequestUpdateInterfaceMessage>(UpdateRingerUserInterfaceDriver);
  41. SubscribeLocalEvent<RingerComponent, CurrencyInsertAttemptEvent>(OnCurrencyInsert);
  42. }
  43. //Event Functions
  44. private void OnCurrencyInsert(EntityUid uid, RingerComponent ringer, CurrencyInsertAttemptEvent args)
  45. {
  46. if (!TryComp<RingerUplinkComponent>(uid, out var uplink))
  47. {
  48. args.Cancel();
  49. return;
  50. }
  51. // if the store can be locked, it must be unlocked first before inserting currency. Stops traitor checking.
  52. if (!uplink.Unlocked)
  53. args.Cancel();
  54. }
  55. private void RingerPlayRingtone(EntityUid uid, RingerComponent ringer, RingerPlayRingtoneMessage args)
  56. {
  57. EnsureComp<ActiveRingerComponent>(uid);
  58. _popupSystem.PopupEntity(Loc.GetString("comp-ringer-vibration-popup"), uid, Filter.Pvs(uid, 0.05f), false, PopupType.Small);
  59. UpdateRingerUserInterface(uid, ringer, true);
  60. }
  61. public void RingerPlayRingtone(Entity<RingerComponent?> ent)
  62. {
  63. if (!Resolve(ent, ref ent.Comp))
  64. return;
  65. EnsureComp<ActiveRingerComponent>(ent);
  66. _popupSystem.PopupEntity(Loc.GetString("comp-ringer-vibration-popup"), ent, Filter.Pvs(ent, 0.05f), false, PopupType.Medium);
  67. UpdateRingerUserInterface(ent, ent.Comp, true);
  68. }
  69. private void UpdateRingerUserInterfaceDriver(EntityUid uid, RingerComponent ringer, RingerRequestUpdateInterfaceMessage args)
  70. {
  71. UpdateRingerUserInterface(uid, ringer, HasComp<ActiveRingerComponent>(uid));
  72. }
  73. private void OnSetRingtone(EntityUid uid, RingerComponent ringer, RingerSetRingtoneMessage args)
  74. {
  75. if (!TryComp(args.Actor, out ActorComponent? actorComp))
  76. return;
  77. ref var lastSetAt = ref CollectionsMarshal.GetValueRefOrAddDefault(_lastSetRingtoneAt, actorComp.PlayerSession.UserId, out var exists);
  78. // Delay on the client is 0.333, 0.25 is still enough and gives some leeway in case of small time differences
  79. if (exists && lastSetAt > _gameTiming.CurTime - TimeSpan.FromMilliseconds(250))
  80. return;
  81. lastSetAt = _gameTiming.CurTime;
  82. // Client sent us an updated ringtone so set it to that.
  83. if (args.Ringtone.Length != RingtoneLength)
  84. return;
  85. var ev = new BeforeRingtoneSetEvent(args.Ringtone);
  86. RaiseLocalEvent(uid, ref ev);
  87. if (ev.Handled)
  88. return;
  89. UpdateRingerRingtone(uid, ringer, args.Ringtone);
  90. }
  91. private void OnSetUplinkRingtone(EntityUid uid, RingerUplinkComponent uplink, ref BeforeRingtoneSetEvent args)
  92. {
  93. if (uplink.Code.SequenceEqual(args.Ringtone) && HasComp<StoreComponent>(uid))
  94. {
  95. uplink.Unlocked = !uplink.Unlocked;
  96. if (TryComp<PdaComponent>(uid, out var pda))
  97. _pda.UpdatePdaUi(uid, pda);
  98. // can't keep store open after locking it
  99. if (!uplink.Unlocked)
  100. _ui.CloseUi(uid, StoreUiKey.Key);
  101. // no saving the code to prevent meta click set on sus guys pda -> wewlad
  102. args.Handled = true;
  103. }
  104. }
  105. /// <summary>
  106. /// Locks the uplink and closes the window, if its open
  107. /// </summary>
  108. /// <remarks>
  109. /// Will not update the PDA ui so you must do that yourself if needed
  110. /// </remarks>
  111. public void LockUplink(EntityUid uid, RingerUplinkComponent? uplink)
  112. {
  113. if (!Resolve(uid, ref uplink, true))
  114. return;
  115. uplink.Unlocked = false;
  116. _ui.CloseUi(uid, StoreUiKey.Key);
  117. }
  118. public void RandomizeRingtone(EntityUid uid, RingerComponent ringer, MapInitEvent args)
  119. {
  120. UpdateRingerRingtone(uid, ringer, GenerateRingtone());
  121. }
  122. public void RandomizeUplinkCode(EntityUid uid, RingerUplinkComponent uplink, ComponentInit args)
  123. {
  124. uplink.Code = GenerateRingtone();
  125. }
  126. //Non Event Functions
  127. private Note[] GenerateRingtone()
  128. {
  129. // Default to using C pentatonic so it at least sounds not terrible.
  130. return GenerateRingtone(new[]
  131. {
  132. Note.C,
  133. Note.D,
  134. Note.E,
  135. Note.G,
  136. Note.A
  137. });
  138. }
  139. private Note[] GenerateRingtone(Note[] notes)
  140. {
  141. var ringtone = new Note[RingtoneLength];
  142. for (var i = 0; i < RingtoneLength; i++)
  143. {
  144. ringtone[i] = _random.Pick(notes);
  145. }
  146. return ringtone;
  147. }
  148. private bool UpdateRingerRingtone(EntityUid uid, RingerComponent ringer, Note[] ringtone)
  149. {
  150. // Assume validation has already happened.
  151. ringer.Ringtone = ringtone;
  152. UpdateRingerUserInterface(uid, ringer, HasComp<ActiveRingerComponent>(uid));
  153. return true;
  154. }
  155. private void UpdateRingerUserInterface(EntityUid uid, RingerComponent ringer, bool isPlaying)
  156. {
  157. _ui.SetUiState(uid, RingerUiKey.Key, new RingerUpdateState(isPlaying, ringer.Ringtone));
  158. }
  159. public bool ToggleRingerUI(EntityUid uid, EntityUid actor)
  160. {
  161. _ui.TryToggleUi(uid, RingerUiKey.Key, actor);
  162. return true;
  163. }
  164. public override void Update(float frameTime) //Responsible for actually playing the ringtone
  165. {
  166. var remove = new RemQueue<EntityUid>();
  167. var pdaQuery = EntityQueryEnumerator<RingerComponent, ActiveRingerComponent>();
  168. while (pdaQuery.MoveNext(out var uid, out var ringer, out var _))
  169. {
  170. ringer.TimeElapsed += frameTime;
  171. if (ringer.TimeElapsed < NoteDelay)
  172. continue;
  173. ringer.TimeElapsed -= NoteDelay;
  174. var ringerXform = Transform(uid);
  175. _audio.PlayEntity(
  176. GetSound(ringer.Ringtone[ringer.NoteCount]),
  177. Filter.Empty().AddInRange(_transform.GetMapCoordinates(uid, ringerXform), ringer.Range),
  178. uid,
  179. true,
  180. AudioParams.Default.WithMaxDistance(ringer.Range).WithVolume(ringer.Volume)
  181. );
  182. ringer.NoteCount++;
  183. if (ringer.NoteCount > RingtoneLength - 1)
  184. {
  185. remove.Add(uid);
  186. UpdateRingerUserInterface(uid, ringer, false);
  187. ringer.TimeElapsed = 0;
  188. ringer.NoteCount = 0;
  189. break;
  190. }
  191. }
  192. foreach (var ent in remove)
  193. {
  194. RemComp<ActiveRingerComponent>(ent);
  195. }
  196. }
  197. private static string GetSound(Note note)
  198. {
  199. return new ResPath("/Audio/Effects/RingtoneNotes/" + note.ToString().ToLower()) + ".ogg";
  200. }
  201. }
  202. [ByRefEvent]
  203. public record struct BeforeRingtoneSetEvent(Note[] Ringtone, bool Handled = false);
  204. }