* Add a way to disable voting for maximum authoritarianism * Allow disabling specific vote types via cvars * Migrate standard-vote-to-cvar dictionary to being static
235 lines
7.4 KiB
C#
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 class VoteEntry
|
|
{
|
|
public string Text { get; }
|
|
public int Votes { get; set; }
|
|
|
|
public VoteEntry(string text)
|
|
{
|
|
Text = text;
|
|
}
|
|
}
|
|
}
|
|
}
|