Files
tbd-station-14/Content.Client/Voting/VoteManager.cs
Pieter-Jan Briers cea87d6985 Voting (#3185)
* Basic voting

* Rewrite lobby in XAML.
Working lobby voting.

* Escape menu is now XAML.

* Vote menu works, custom votes, gamemode votes.

* Vote timeouts & administration.

Basically done now.

* I will now pretend I was never planning to code voting hotkeys.

* Make vote call UI a bit... funny.

* Fix exception on round restart.

* Fix some vote command definitions.
2021-02-17 01:07:17 +11:00

197 lines
6.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Network.NetMessages;
using Robust.Client.Console;
using Robust.Client.UserInterface;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Timing;
#nullable enable
namespace Content.Client.Voting
{
public interface IVoteManager
{
void Initialize();
void SendCastVote(int voteId, int option);
void ClearPopupContainer();
void SetPopupContainer(Control container);
bool CanCallVote { get; }
event Action<bool> CanCallVoteChanged;
}
public sealed class VoteManager : IVoteManager
{
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;
private readonly Dictionary<int, ActiveVote> _votes = new();
private readonly Dictionary<int, VotePopup> _votePopups = new();
private Control? _popupContainer;
public bool CanCallVote { get; private set; }
public event Action<bool>? CanCallVoteChanged;
public void Initialize()
{
_netManager.RegisterNetMessage<MsgVoteData>(MsgVoteData.NAME, ReceiveVoteData);
_netManager.RegisterNetMessage<MsgVoteCanCall>(MsgVoteCanCall.NAME, ReceiveVoteCanCall);
}
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 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;
// 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 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)
return;
CanCallVote = message.CanCall;
CanCallVoteChanged?.Invoke(CanCallVote);
}
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 class VoteEntry
{
public string Text { get; }
public int Votes { get; set; }
public VoteEntry(string text)
{
Text = text;
}
}
}
}