diff --git a/Content.Client/Voting/UI/VoteCallMenu.xaml b/Content.Client/Voting/UI/VoteCallMenu.xaml
index 5ff1015dbb..a5e1204521 100644
--- a/Content.Client/Voting/UI/VoteCallMenu.xaml
+++ b/Content.Client/Voting/UI/VoteCallMenu.xaml
@@ -12,11 +12,14 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Voting/UI/VoteCallMenu.xaml.cs b/Content.Client/Voting/UI/VoteCallMenu.xaml.cs
index f276d9c1f0..58c56ace4c 100644
--- a/Content.Client/Voting/UI/VoteCallMenu.xaml.cs
+++ b/Content.Client/Voting/UI/VoteCallMenu.xaml.cs
@@ -1,4 +1,5 @@
using Content.Client.Stylesheets;
+using Content.Shared.Voting;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
@@ -9,6 +10,7 @@ using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
+using Robust.Shared.Timing;
namespace Content.Client.Voting.UI
{
@@ -16,12 +18,15 @@ namespace Content.Client.Voting.UI
public partial class VoteCallMenu : BaseWindow
{
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
+ [Dependency] private readonly IVoteManager _voteManager = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
- public static readonly (string name, string id, (string name, string id)[]? secondaries)[] AvailableVoteTypes =
- {
- ("ui-vote-type-restart", "restart", null),
- ("ui-vote-type-gamemode", "preset", null)
- };
+ public static readonly (string name, StandardVoteType type, (string name, string id)[]? secondaries)[]
+ AvailableVoteTypes =
+ {
+ ("ui-vote-type-restart", StandardVoteType.Restart, null),
+ ("ui-vote-type-gamemode", StandardVoteType.Preset, null)
+ };
public VoteCallMenu()
{
@@ -42,6 +47,33 @@ namespace Content.Client.Voting.UI
CreateButton.OnPressed += CreatePressed;
}
+ protected override void Opened()
+ {
+ base.Opened();
+
+ _voteManager.CanCallVoteChanged += CanCallVoteChanged;
+ }
+
+ public override void Close()
+ {
+ base.Close();
+
+ _voteManager.CanCallVoteChanged -= CanCallVoteChanged;
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ UpdateVoteTimeout();
+ }
+
+ private void CanCallVoteChanged(bool obj)
+ {
+ if (!obj)
+ Close();
+ }
+
private void CreatePressed(BaseButton.ButtonEventArgs obj)
{
var typeId = VoteTypeButton.SelectedId;
@@ -62,6 +94,20 @@ namespace Content.Client.Voting.UI
Close();
}
+ private void UpdateVoteTimeout()
+ {
+ var (_, typeKey, _) = AvailableVoteTypes[VoteTypeButton.SelectedId];
+ var isAvailable = _voteManager.CanCallStandardVote(typeKey, out var timeout);
+ CreateButton.Disabled = !isAvailable;
+ VoteTypeTimeoutLabel.Visible = !isAvailable;
+
+ if (!isAvailable)
+ {
+ var remaining = timeout - _gameTiming.RealTime;
+ VoteTypeTimeoutLabel.Text = Loc.GetString("ui-vote-type-timeout", ("remaining", remaining.ToString("mm\\:ss")));
+ }
+ }
+
private static void VoteSecondSelected(OptionButton.ItemSelectedEventArgs obj)
{
obj.Button.SelectId(obj.Id);
diff --git a/Content.Client/Voting/VoteManager.cs b/Content.Client/Voting/VoteManager.cs
index ab7dbe53f7..bd70402fb0 100644
--- a/Content.Client/Voting/VoteManager.cs
+++ b/Content.Client/Voting/VoteManager.cs
@@ -19,7 +19,10 @@ namespace Content.Client.Voting
void ClearPopupContainer();
void SetPopupContainer(Control container);
bool CanCallVote { get; }
+
+ bool CanCallStandardVote(StandardVoteType type, out TimeSpan whenCan);
event Action CanCallVoteChanged;
+ event Action CanCallStandardVotesChanged;
}
public sealed class VoteManager : IVoteManager
@@ -29,13 +32,17 @@ namespace Content.Client.Voting
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly IBaseClient _client = default!;
+ private readonly Dictionary _standardVoteTimeouts = new();
private readonly Dictionary _votes = new();
private readonly Dictionary _votePopups = new();
private Control? _popupContainer;
public bool CanCallVote { get; private set; }
+
public event Action? CanCallVoteChanged;
+ public event Action? CanCallStandardVotesChanged;
+
public void Initialize()
{
_netManager.RegisterNetMessage(ReceiveVoteData);
@@ -54,6 +61,11 @@ namespace Content.Client.Voting
}
}
+ public bool CanCallStandardVote(StandardVoteType type, out TimeSpan whenCan)
+ {
+ return !_standardVoteTimeouts.TryGetValue(type, out whenCan);
+ }
+
public void ClearPopupContainer()
{
if (_popupContainer == null)
@@ -161,11 +173,20 @@ namespace Content.Client.Voting
private void ReceiveVoteCanCall(MsgVoteCanCall message)
{
- if (CanCallVote == message.CanCall)
- return;
+ if (CanCallVote != message.CanCall)
+ {
+ // TODO: actually use the "when can call vote" time for UI display or something.
+ CanCallVote = message.CanCall;
+ CanCallVoteChanged?.Invoke(CanCallVote);
+ }
- CanCallVote = message.CanCall;
- CanCallVoteChanged?.Invoke(CanCallVote);
+ _standardVoteTimeouts.Clear();
+ foreach (var (type, time) in message.VotesUnavailable)
+ {
+ _standardVoteTimeouts.Add(type, _gameTiming.RealServerToLocal(time));
+ }
+
+ CanCallStandardVotesChanged?.Invoke();
}
public void SendCastVote(int voteId, int option)
diff --git a/Content.Server/Voting/IVoteHandle.cs b/Content.Server/Voting/IVoteHandle.cs
index b4d653f193..9242b9f6e0 100644
--- a/Content.Server/Voting/IVoteHandle.cs
+++ b/Content.Server/Voting/IVoteHandle.cs
@@ -1,21 +1,87 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using Content.Server.Voting.Managers;
using Robust.Server.Player;
namespace Content.Server.Voting
{
+ ///
+ /// A handle to vote, active or past.
+ ///
+ ///
+ ///
+ /// Vote options are referred to by UI/networking as integer IDs.
+ /// These IDs are the index of the vote option in the list
+ /// used to create the vote.
+ ///
+ ///
public interface IVoteHandle
{
+ ///
+ /// The numeric ID of the vote. Can be used in .
+ ///
int Id { get; }
+
+ ///
+ /// The title of the vote.
+ ///
string Title { get; }
+
+ ///
+ /// Text representing who/what initiated the vote.
+ ///
string InitiatorText { get; }
+
+ ///
+ /// Whether the vote has finished and is no longer active.
+ ///
bool Finished { get; }
+
+ ///
+ /// Whether the vote was cancelled by an administrator and did not finish naturally.
+ ///
+ ///
+ /// If this is true, is also true.
+ ///
bool Cancelled { get; }
+ ///
+ /// Current count of votes per option type.
+ ///
IReadOnlyDictionary