Files
tbd-station-14/Content.Client/Voting/VoteManager.cs
2022-02-16 18:23:23 +11:00

235 lines
7.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Voting;
using Robust.Client;
using Robust.Client.Console;
using Robust.Client.UserInterface;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Player;
using Robust.Shared.Audio;
namespace Content.Client.Voting
{
public interface IVoteManager
{
void Initialize();
void SendCastVote(int voteId, int option);
void ClearPopupContainer();
void SetPopupContainer(Control container);
bool CanCallVote { get; }
bool CanCallStandardVote(StandardVoteType type, out TimeSpan whenCan);
event Action<bool> CanCallVoteChanged;
event Action CanCallStandardVotesChanged;
}
public sealed class VoteManager : IVoteManager
{
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly IBaseClient _client = default!;
private readonly Dictionary<StandardVoteType, TimeSpan> _standardVoteTimeouts = new();
private readonly Dictionary<int, ActiveVote> _votes = new();
private readonly Dictionary<int, UI.VotePopup> _votePopups = new();
private Control? _popupContainer;
public bool CanCallVote { get; private set; }
public event Action<bool>? CanCallVoteChanged;
public event Action? CanCallStandardVotesChanged;
public void Initialize()
{
_netManager.RegisterNetMessage<MsgVoteData>(ReceiveVoteData);
_netManager.RegisterNetMessage<MsgVoteCanCall>(ReceiveVoteCanCall);
_client.RunLevelChanged += ClientOnRunLevelChanged;
}
private void ClientOnRunLevelChanged(object? sender, RunLevelChangedEventArgs e)
{
// Clear votes on disconnect.
if (e.NewLevel == ClientRunLevel.Initialize)
{
ClearPopupContainer();
_votes.Clear();
}
}
public bool CanCallStandardVote(StandardVoteType type, out TimeSpan whenCan)
{
return !_standardVoteTimeouts.TryGetValue(type, out whenCan);
}
public void ClearPopupContainer()
{
if (_popupContainer == null)
return;
if (!_popupContainer.Disposed)
{
foreach (var popup in _votePopups.Values)
{
popup.Orphan();
}
}
_votePopups.Clear();
_popupContainer = null;
}
public void SetPopupContainer(Control container)
{
if (_popupContainer != null)
{
ClearPopupContainer();
}
_popupContainer = container;
foreach (var (vId, vote) in _votes)
{
var popup = new UI.VotePopup(vote);
_votePopups.Add(vId, popup);
_popupContainer.AddChild(popup);
popup.UpdateData();
}
}
private void ReceiveVoteData(MsgVoteData message)
{
var @new = false;
var voteId = message.VoteId;
if (!_votes.TryGetValue(voteId, out var existingVote))
{
if (!message.VoteActive)
{
// Got "vote inactive" for nonexistent vote???
return;
}
@new = true;
SoundSystem.Play(Filter.Local(), "/Audio/Effects/voteding.ogg");
// New vote from the server.
var vote = new ActiveVote(voteId)
{
Entries = message.Options
.Select(c => new VoteEntry(c.name))
.ToArray()
};
existingVote = vote;
_votes.Add(voteId, vote);
}
else if (!message.VoteActive)
{
// Remove gone vote.
_votes.Remove(voteId);
if (_votePopups.TryGetValue(voteId, out var toRemove))
{
toRemove.Orphan();
_votePopups.Remove(voteId);
}
return;
}
// Update vote data from incoming.
if (message.IsYourVoteDirty)
existingVote.OurVote = message.YourVote;
// On the server, most of these params can't change.
// It can't hurt to just re-set this stuff since I'm lazy and the server is sending it anyways, so...
existingVote.Initiator = message.VoteInitiator;
existingVote.Title = message.VoteTitle;
existingVote.StartTime = _gameTiming.RealServerToLocal(message.StartTime);
existingVote.EndTime = _gameTiming.RealServerToLocal(message.EndTime);
// Logger.Debug($"{existingVote.StartTime}, {existingVote.EndTime}, {_gameTiming.RealTime}");
for (var i = 0; i < message.Options.Length; i++)
{
existingVote.Entries[i].Votes = message.Options[i].votes;
}
if (@new && _popupContainer != null)
{
var popup = new UI.VotePopup(existingVote);
_votePopups.Add(voteId, popup);
_popupContainer.AddChild(popup);
}
if (_votePopups.TryGetValue(voteId, out var ePopup))
{
ePopup.UpdateData();
}
}
private void ReceiveVoteCanCall(MsgVoteCanCall message)
{
if (CanCallVote != message.CanCall)
{
// TODO: actually use the "when can call vote" time for UI display or something.
CanCallVote = message.CanCall;
CanCallVoteChanged?.Invoke(CanCallVote);
}
_standardVoteTimeouts.Clear();
foreach (var (type, time) in message.VotesUnavailable)
{
var fixedTime = (time == TimeSpan.Zero) ? time : _gameTiming.RealServerToLocal(time);
_standardVoteTimeouts.Add(type, fixedTime);
}
CanCallStandardVotesChanged?.Invoke();
}
public void SendCastVote(int voteId, int option)
{
var data = _votes[voteId];
// Update immediately to avoid any funny reconciliation bugs.
// See also code in server side to avoid bulldozing this.
data.OurVote = option;
_console.LocalShell.RemoteExecuteCommand($"vote {voteId} {option}");
}
public sealed class ActiveVote
{
public VoteEntry[] Entries = default!;
// Both of these are local RealTime (converted at NetMsg receive).
public TimeSpan StartTime;
public TimeSpan EndTime;
public string Title = "";
public string Initiator = "";
public int? OurVote;
public int Id;
public ActiveVote(int voteId)
{
Id = voteId;
}
}
public sealed class VoteEntry
{
public string Text { get; }
public int Votes { get; set; }
public VoteEntry(string text)
{
Text = text;
}
}
}
}