1
0

VoteManager.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Content.Shared.Voting;
  5. using Robust.Client;
  6. using Robust.Client.Audio;
  7. using Robust.Client.Console;
  8. using Robust.Client.GameObjects;
  9. using Robust.Client.ResourceManagement;
  10. using Robust.Client.UserInterface;
  11. using Robust.Shared.IoC;
  12. using Robust.Shared.Network;
  13. using Robust.Shared.Timing;
  14. using Robust.Shared.Player;
  15. using Robust.Shared.Audio;
  16. using Robust.Shared.Audio.Sources;
  17. using Robust.Shared.ContentPack;
  18. namespace Content.Client.Voting
  19. {
  20. public interface IVoteManager
  21. {
  22. void Initialize();
  23. void SendCastVote(int voteId, int option);
  24. void ClearPopupContainer();
  25. void SetPopupContainer(Control container);
  26. bool CanCallVote { get; }
  27. bool CanCallStandardVote(StandardVoteType type, out TimeSpan whenCan);
  28. event Action<bool> CanCallVoteChanged;
  29. event Action CanCallStandardVotesChanged;
  30. }
  31. public sealed class VoteManager : IVoteManager
  32. {
  33. [Dependency] private readonly IAudioManager _audio = default!;
  34. [Dependency] private readonly IBaseClient _client = default!;
  35. [Dependency] private readonly IClientConsoleHost _console = default!;
  36. [Dependency] private readonly IClientNetManager _netManager = default!;
  37. [Dependency] private readonly IGameTiming _gameTiming = default!;
  38. [Dependency] private readonly IResourceCache _res = default!;
  39. private readonly Dictionary<StandardVoteType, TimeSpan> _standardVoteTimeouts = new();
  40. private readonly Dictionary<int, ActiveVote> _votes = new();
  41. private readonly Dictionary<int, UI.VotePopup> _votePopups = new();
  42. private Control? _popupContainer;
  43. private IAudioSource? _voteSource;
  44. public bool CanCallVote { get; private set; }
  45. public event Action<bool>? CanCallVoteChanged;
  46. public event Action? CanCallStandardVotesChanged;
  47. public void Initialize()
  48. {
  49. const string sound = "/Audio/Effects/voteding.ogg";
  50. _voteSource = _audio.CreateAudioSource(_res.GetResource<AudioResource>(sound));
  51. if (_voteSource != null)
  52. {
  53. _voteSource.Global = true;
  54. }
  55. _netManager.RegisterNetMessage<MsgVoteData>(ReceiveVoteData);
  56. _netManager.RegisterNetMessage<MsgVoteCanCall>(ReceiveVoteCanCall);
  57. _client.RunLevelChanged += ClientOnRunLevelChanged;
  58. }
  59. private void ClientOnRunLevelChanged(object? sender, RunLevelChangedEventArgs e)
  60. {
  61. // Clear votes on disconnect.
  62. if (e.NewLevel == ClientRunLevel.Initialize)
  63. {
  64. ClearPopupContainer();
  65. _votes.Clear();
  66. }
  67. }
  68. public bool CanCallStandardVote(StandardVoteType type, out TimeSpan whenCan)
  69. {
  70. return !_standardVoteTimeouts.TryGetValue(type, out whenCan);
  71. }
  72. public void ClearPopupContainer()
  73. {
  74. if (_popupContainer == null)
  75. return;
  76. if (!_popupContainer.Disposed)
  77. {
  78. foreach (var popup in _votePopups.Values)
  79. {
  80. popup.Orphan();
  81. }
  82. }
  83. _votePopups.Clear();
  84. _popupContainer = null;
  85. }
  86. public void SetPopupContainer(Control container)
  87. {
  88. if (_popupContainer != null)
  89. {
  90. ClearPopupContainer();
  91. }
  92. _popupContainer = container;
  93. SetVoteData();
  94. }
  95. private void SetVoteData()
  96. {
  97. if (_popupContainer == null)
  98. return;
  99. foreach (var (vId, vote) in _votes)
  100. {
  101. var popup = new UI.VotePopup(vote);
  102. _votePopups.Add(vId, popup);
  103. _popupContainer.AddChild(popup);
  104. popup.UpdateData();
  105. }
  106. }
  107. private void ReceiveVoteData(MsgVoteData message)
  108. {
  109. var @new = false;
  110. var voteId = message.VoteId;
  111. if (!_votes.TryGetValue(voteId, out var existingVote))
  112. {
  113. if (!message.VoteActive)
  114. {
  115. // Got "vote inactive" for nonexistent vote???
  116. return;
  117. }
  118. _voteSource?.Restart();
  119. @new = true;
  120. // Refresh
  121. var container = _popupContainer;
  122. ClearPopupContainer();
  123. if (container != null)
  124. SetPopupContainer(container);
  125. // New vote from the server.
  126. var vote = new ActiveVote(voteId)
  127. {
  128. Entries = message.Options
  129. .Select(c => new VoteEntry(c.name))
  130. .ToArray()
  131. };
  132. existingVote = vote;
  133. _votes.Add(voteId, vote);
  134. }
  135. else if (!message.VoteActive)
  136. {
  137. // Remove gone vote.
  138. _votes.Remove(voteId);
  139. if (_votePopups.TryGetValue(voteId, out var toRemove))
  140. {
  141. toRemove.Orphan();
  142. _votePopups.Remove(voteId);
  143. }
  144. return;
  145. }
  146. // Update vote data from incoming.
  147. if (message.IsYourVoteDirty)
  148. existingVote.OurVote = message.YourVote;
  149. // On the server, most of these params can't change.
  150. // It can't hurt to just re-set this stuff since I'm lazy and the server is sending it anyways, so...
  151. existingVote.Initiator = message.VoteInitiator;
  152. existingVote.Title = message.VoteTitle;
  153. existingVote.StartTime = _gameTiming.RealServerToLocal(message.StartTime);
  154. existingVote.EndTime = _gameTiming.RealServerToLocal(message.EndTime);
  155. existingVote.DisplayVotes = message.DisplayVotes;
  156. existingVote.TargetEntity = message.TargetEntity;
  157. // Logger.Debug($"{existingVote.StartTime}, {existingVote.EndTime}, {_gameTiming.RealTime}");
  158. for (var i = 0; i < message.Options.Length; i++)
  159. {
  160. existingVote.Entries[i].Votes = message.Options[i].votes;
  161. }
  162. if (@new && _popupContainer != null)
  163. {
  164. var popup = new UI.VotePopup(existingVote);
  165. _votePopups.Add(voteId, popup);
  166. _popupContainer.AddChild(popup);
  167. }
  168. if (_votePopups.TryGetValue(voteId, out var ePopup))
  169. {
  170. ePopup.UpdateData();
  171. }
  172. }
  173. private void ReceiveVoteCanCall(MsgVoteCanCall message)
  174. {
  175. if (CanCallVote != message.CanCall)
  176. {
  177. // TODO: actually use the "when can call vote" time for UI display or something.
  178. CanCallVote = message.CanCall;
  179. CanCallVoteChanged?.Invoke(CanCallVote);
  180. }
  181. _standardVoteTimeouts.Clear();
  182. foreach (var (type, time) in message.VotesUnavailable)
  183. {
  184. var fixedTime = (time == TimeSpan.Zero) ? time : _gameTiming.RealServerToLocal(time);
  185. _standardVoteTimeouts.Add(type, fixedTime);
  186. }
  187. CanCallStandardVotesChanged?.Invoke();
  188. }
  189. public void SendCastVote(int voteId, int option)
  190. {
  191. var data = _votes[voteId];
  192. // Update immediately to avoid any funny reconciliation bugs.
  193. // See also code in server side to avoid bulldozing this.
  194. data.OurVote = option;
  195. _console.LocalShell.RemoteExecuteCommand($"vote {voteId} {option}");
  196. }
  197. public sealed class ActiveVote
  198. {
  199. public VoteEntry[] Entries = default!;
  200. // Both of these are local RealTime (converted at NetMsg receive).
  201. public TimeSpan StartTime;
  202. public TimeSpan EndTime;
  203. public string Title = "";
  204. public string Initiator = "";
  205. public int? OurVote;
  206. public int Id;
  207. public bool DisplayVotes;
  208. public int? TargetEntity; // NetEntity
  209. public ActiveVote(int voteId)
  210. {
  211. Id = voteId;
  212. }
  213. }
  214. public sealed class VoteEntry
  215. {
  216. public string Text { get; }
  217. public int Votes { get; set; }
  218. public VoteEntry(string text)
  219. {
  220. Text = text;
  221. }
  222. }
  223. }
  224. }