Add Votekick functionality (#32005)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<ui:VoteCallMenu xmlns="https://spacestation14.io"
|
||||
<ui:VoteCallMenu xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.Voting.UI"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MouseFilter="Stop" MinSize="350 150">
|
||||
MouseFilter="Stop" MinSize="350 200">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Margin="8 0" Orientation="Horizontal">
|
||||
@@ -13,16 +13,18 @@
|
||||
<controls:HighDivider />
|
||||
|
||||
<BoxContainer Orientation="Vertical" Margin="8 2 8 0" VerticalExpand="True" VerticalAlignment="Top">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<OptionButton Name="VoteTypeButton" HorizontalExpand="True" />
|
||||
<Control HorizontalExpand="True">
|
||||
<OptionButton Name="VoteSecondButton" Visible="False" />
|
||||
</Control>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<OptionButton Margin="2 1" Name="VoteTypeButton" HorizontalExpand="False" />
|
||||
<BoxContainer Name="VoteOptionsButtonContainer" HorizontalExpand="False" Orientation="Vertical">
|
||||
</BoxContainer>
|
||||
<Button Margin="64 4" Name="FollowButton" Text="{Loc 'ui-vote-follow-button'}" Visible="False" />
|
||||
<Label Margin="2 2" Name="VoteNotTrustedLabel" Text="{Loc 'ui-vote-trusted-users-notice'}" Visible="False" />
|
||||
<Label Margin="2 2" Name="VoteWarningLabel" Text="{Loc 'ui-vote-abuse-warning'}" Visible="False" HorizontalAlignment="Center"/>
|
||||
</BoxContainer>
|
||||
<Label Name="VoteTypeTimeoutLabel" Visible="False" />
|
||||
<Label Margin="8 2" Name="VoteTypeTimeoutLabel" Visible="False" />
|
||||
</BoxContainer>
|
||||
|
||||
<Button Margin="8 2" Name="CreateButton" Text="{Loc 'ui-vote-create-button'}" />
|
||||
|
||||
<Button Margin="8 32 8 2" Name="CreateButton" Text="{Loc 'ui-vote-create-button'}" />
|
||||
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
<Label Margin="12 0 0 0" StyleClasses="LabelSubText" Text="{Loc 'ui-vote-fluff'}" />
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Voting;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
@@ -9,10 +11,8 @@ using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -25,32 +25,54 @@ namespace Content.Client.Voting.UI
|
||||
[Dependency] private readonly IVoteManager _voteManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityNetworkManager _entNetManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
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),
|
||||
("ui-vote-type-map", StandardVoteType.Map, null)
|
||||
};
|
||||
private VotingSystem _votingSystem;
|
||||
|
||||
public StandardVoteType Type;
|
||||
|
||||
public Dictionary<StandardVoteType, CreateVoteOption> AvailableVoteOptions = new Dictionary<StandardVoteType, CreateVoteOption>()
|
||||
{
|
||||
{ StandardVoteType.Restart, new CreateVoteOption("ui-vote-type-restart", new(), false, null) },
|
||||
{ StandardVoteType.Preset, new CreateVoteOption("ui-vote-type-gamemode", new(), false, null) },
|
||||
{ StandardVoteType.Map, new CreateVoteOption("ui-vote-type-map", new(), false, null) },
|
||||
{ StandardVoteType.Votekick, new CreateVoteOption("ui-vote-type-votekick", new(), true, 0) }
|
||||
};
|
||||
|
||||
public Dictionary<string, string> VotekickReasons = new Dictionary<string, string>()
|
||||
{
|
||||
{ VotekickReasonType.Raiding.ToString(), Loc.GetString("ui-vote-votekick-type-raiding") },
|
||||
{ VotekickReasonType.Cheating.ToString(), Loc.GetString("ui-vote-votekick-type-cheating") },
|
||||
{ VotekickReasonType.Spam.ToString(), Loc.GetString("ui-vote-votekick-type-spamming") }
|
||||
};
|
||||
|
||||
public Dictionary<NetUserId, (NetEntity, string)> PlayerList = new();
|
||||
|
||||
public OptionButton? _followDropdown = null;
|
||||
|
||||
public bool IsAllowedVotekick = false;
|
||||
|
||||
public VoteCallMenu()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
_votingSystem = _entityManager.System<VotingSystem>();
|
||||
|
||||
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
||||
CloseButton.OnPressed += _ => Close();
|
||||
VoteNotTrustedLabel.Text = Loc.GetString("ui-vote-trusted-users-notice", ("timeReq", _cfg.GetCVar(CCVars.VotekickEligibleVoterDeathtime) / 60));
|
||||
|
||||
for (var i = 0; i < AvailableVoteTypes.Length; i++)
|
||||
foreach (StandardVoteType voteType in Enum.GetValues<StandardVoteType>())
|
||||
{
|
||||
var (text, _, _) = AvailableVoteTypes[i];
|
||||
VoteTypeButton.AddItem(Loc.GetString(text), i);
|
||||
var option = AvailableVoteOptions[voteType];
|
||||
VoteTypeButton.AddItem(Loc.GetString(option.Name), (int)voteType);
|
||||
}
|
||||
|
||||
VoteTypeButton.OnItemSelected += VoteTypeSelected;
|
||||
VoteSecondButton.OnItemSelected += VoteSecondSelected;
|
||||
CreateButton.OnPressed += CreatePressed;
|
||||
FollowButton.OnPressed += FollowSelected;
|
||||
}
|
||||
|
||||
protected override void Opened()
|
||||
@@ -60,6 +82,8 @@ namespace Content.Client.Voting.UI
|
||||
_netManager.ClientSendMessage(new MsgVoteMenu());
|
||||
|
||||
_voteManager.CanCallVoteChanged += CanCallVoteChanged;
|
||||
_votingSystem.VotePlayerListResponse += UpdateVotePlayerList;
|
||||
_votingSystem.RequestVotePlayerList();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
@@ -67,6 +91,7 @@ namespace Content.Client.Voting.UI
|
||||
base.Close();
|
||||
|
||||
_voteManager.CanCallVoteChanged -= CanCallVoteChanged;
|
||||
_votingSystem.VotePlayerListResponse -= UpdateVotePlayerList;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
@@ -82,21 +107,50 @@ namespace Content.Client.Voting.UI
|
||||
Close();
|
||||
}
|
||||
|
||||
private void UpdateVotePlayerList(VotePlayerListResponseEvent msg)
|
||||
{
|
||||
Dictionary<string, string> optionsList = new();
|
||||
Dictionary<NetUserId, (NetEntity, string)> playerList = new();
|
||||
foreach ((NetUserId, NetEntity, string) player in msg.Players)
|
||||
{
|
||||
optionsList.Add(player.Item1.ToString(), player.Item3);
|
||||
playerList.Add(player.Item1, (player.Item2, player.Item3));
|
||||
}
|
||||
if (optionsList.Count == 0)
|
||||
optionsList.Add(" ", " ");
|
||||
|
||||
PlayerList = playerList;
|
||||
|
||||
IsAllowedVotekick = !msg.Denied;
|
||||
|
||||
var updatedDropdownOption = AvailableVoteOptions[StandardVoteType.Votekick];
|
||||
updatedDropdownOption.Dropdowns = new List<Dictionary<string, string>>() { optionsList, VotekickReasons };
|
||||
AvailableVoteOptions[StandardVoteType.Votekick] = updatedDropdownOption;
|
||||
}
|
||||
|
||||
private void CreatePressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
var typeId = VoteTypeButton.SelectedId;
|
||||
var (_, typeKey, secondaries) = AvailableVoteTypes[typeId];
|
||||
var voteType = AvailableVoteOptions[(StandardVoteType)typeId];
|
||||
|
||||
if (secondaries != null)
|
||||
var commandArgs = "";
|
||||
|
||||
if (voteType.Dropdowns == null || voteType.Dropdowns.Count == 0)
|
||||
{
|
||||
var secondaryId = VoteSecondButton.SelectedId;
|
||||
var (_, secondKey) = secondaries[secondaryId];
|
||||
|
||||
_consoleHost.LocalShell.RemoteExecuteCommand($"createvote {typeKey} {secondKey}");
|
||||
_consoleHost.LocalShell.RemoteExecuteCommand($"createvote {((StandardVoteType)typeId).ToString()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_consoleHost.LocalShell.RemoteExecuteCommand($"createvote {typeKey}");
|
||||
int i = 0;
|
||||
foreach(var dropdowns in VoteOptionsButtonContainer.Children)
|
||||
{
|
||||
if (dropdowns is OptionButton optionButton && AvailableVoteOptions[(StandardVoteType)typeId].Dropdowns != null)
|
||||
{
|
||||
commandArgs += AvailableVoteOptions[(StandardVoteType)typeId].Dropdowns[i].ElementAt(optionButton.SelectedId).Key + " ";
|
||||
i++;
|
||||
}
|
||||
}
|
||||
_consoleHost.LocalShell.RemoteExecuteCommand($"createvote {((StandardVoteType)typeId).ToString()} {commandArgs}");
|
||||
}
|
||||
|
||||
Close();
|
||||
@@ -104,9 +158,16 @@ namespace Content.Client.Voting.UI
|
||||
|
||||
private void UpdateVoteTimeout()
|
||||
{
|
||||
var (_, typeKey, _) = AvailableVoteTypes[VoteTypeButton.SelectedId];
|
||||
var typeKey = (StandardVoteType)VoteTypeButton.SelectedId;
|
||||
var isAvailable = _voteManager.CanCallStandardVote(typeKey, out var timeout);
|
||||
CreateButton.Disabled = !isAvailable;
|
||||
if (typeKey == StandardVoteType.Votekick && !IsAllowedVotekick)
|
||||
{
|
||||
CreateButton.Disabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateButton.Disabled = !isAvailable;
|
||||
}
|
||||
VoteTypeTimeoutLabel.Visible = !isAvailable;
|
||||
|
||||
if (!isAvailable)
|
||||
@@ -123,29 +184,73 @@ namespace Content.Client.Voting.UI
|
||||
}
|
||||
}
|
||||
|
||||
private static void VoteSecondSelected(OptionButton.ItemSelectedEventArgs obj)
|
||||
private static void ButtonSelected(OptionButton.ItemSelectedEventArgs obj)
|
||||
{
|
||||
obj.Button.SelectId(obj.Id);
|
||||
}
|
||||
|
||||
private void FollowSelected(Button.ButtonEventArgs obj)
|
||||
{
|
||||
if (_followDropdown == null)
|
||||
return;
|
||||
|
||||
if (_followDropdown.SelectedId >= PlayerList.Count)
|
||||
return;
|
||||
|
||||
var netEntity = PlayerList.ElementAt(_followDropdown.SelectedId).Value.Item1;
|
||||
|
||||
var msg = new GhostWarpToTargetRequestEvent(netEntity);
|
||||
_entNetManager.SendSystemNetworkMessage(msg);
|
||||
}
|
||||
|
||||
private void VoteTypeSelected(OptionButton.ItemSelectedEventArgs obj)
|
||||
{
|
||||
VoteTypeButton.SelectId(obj.Id);
|
||||
|
||||
var (_, _, options) = AvailableVoteTypes[obj.Id];
|
||||
if (options == null)
|
||||
VoteNotTrustedLabel.Visible = false;
|
||||
if ((StandardVoteType)obj.Id == StandardVoteType.Votekick)
|
||||
{
|
||||
VoteSecondButton.Visible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
VoteSecondButton.Visible = true;
|
||||
VoteSecondButton.Clear();
|
||||
|
||||
for (var i = 0; i < options.Length; i++)
|
||||
if (!IsAllowedVotekick)
|
||||
{
|
||||
var (text, _) = options[i];
|
||||
VoteSecondButton.AddItem(Loc.GetString(text), i);
|
||||
VoteNotTrustedLabel.Visible = true;
|
||||
var updatedDropdownOption = AvailableVoteOptions[StandardVoteType.Votekick];
|
||||
updatedDropdownOption.Dropdowns = new List<Dictionary<string, string>>();
|
||||
AvailableVoteOptions[StandardVoteType.Votekick] = updatedDropdownOption;
|
||||
}
|
||||
else
|
||||
{
|
||||
_votingSystem.RequestVotePlayerList();
|
||||
}
|
||||
}
|
||||
|
||||
VoteWarningLabel.Visible = AvailableVoteOptions[(StandardVoteType)obj.Id].EnableVoteWarning;
|
||||
FollowButton.Visible = false;
|
||||
|
||||
var voteList = AvailableVoteOptions[(StandardVoteType)obj.Id].Dropdowns;
|
||||
|
||||
VoteOptionsButtonContainer.RemoveAllChildren();
|
||||
if (voteList != null)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var voteDropdown in voteList)
|
||||
{
|
||||
var optionButton = new OptionButton();
|
||||
int j = 0;
|
||||
foreach (var (key, value) in voteDropdown)
|
||||
{
|
||||
optionButton.AddItem(Loc.GetString(value), j);
|
||||
j++;
|
||||
}
|
||||
VoteOptionsButtonContainer.AddChild(optionButton);
|
||||
optionButton.Visible = true;
|
||||
optionButton.OnItemSelected += ButtonSelected;
|
||||
optionButton.Margin = new Thickness(2, 1);
|
||||
if (AvailableVoteOptions[(StandardVoteType)obj.Id].FollowDropdownId != null && AvailableVoteOptions[(StandardVoteType)obj.Id].FollowDropdownId == i)
|
||||
{
|
||||
_followDropdown = optionButton;
|
||||
FollowButton.Visible = true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,4 +273,20 @@ namespace Content.Client.Voting.UI
|
||||
new VoteCallMenu().OpenCentered();
|
||||
}
|
||||
}
|
||||
|
||||
public record struct CreateVoteOption
|
||||
{
|
||||
public string Name;
|
||||
public List<Dictionary<string, string>> Dropdowns;
|
||||
public bool EnableVoteWarning;
|
||||
public int? FollowDropdownId; // If set, this will enable the Follow button and use the dropdown matching the ID as input.
|
||||
|
||||
public CreateVoteOption(string name, List<Dictionary<string, string>> dropdowns, bool enableVoteWarning, int? followDropdownId)
|
||||
{
|
||||
Name = name;
|
||||
Dropdowns = dropdowns;
|
||||
EnableVoteWarning = enableVoteWarning;
|
||||
FollowDropdownId = followDropdownId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<Control xmlns="https://spacestation14.io" MinWidth="300" MaxWidth="500">
|
||||
<Control xmlns="https://spacestation14.io" MinWidth="300" MaxWidth="500">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<BoxContainer Margin="4" Orientation="Vertical">
|
||||
<Label Name="VoteCaller" />
|
||||
<RichTextLabel Name="VoteTitle" />
|
||||
|
||||
<GridContainer Columns="3" Name="VoteOptionsContainer" />
|
||||
<Button Margin="4 4" Name="FollowVoteTarget" Text="{Loc 'ui-vote-follow-button-popup'}" Visible="False"></Button>
|
||||
|
||||
<GridContainer Columns="3" Name="VoteOptionsContainer"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<ProgressBar Margin="4" HorizontalExpand="True" Name="TimeLeftBar" MinValue="0" MaxValue="1" />
|
||||
<Label Name="TimeLeftText" />
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Ghost;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -17,9 +15,11 @@ namespace Content.Client.Voting.UI
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IVoteManager _voteManager = default!;
|
||||
[Dependency] private readonly IEntityNetworkManager _net = default!;
|
||||
|
||||
private readonly VoteManager.ActiveVote _vote;
|
||||
private readonly Button[] _voteButtons;
|
||||
private readonly NetEntity? _targetEntity;
|
||||
|
||||
public VotePopup(VoteManager.ActiveVote vote)
|
||||
{
|
||||
@@ -29,6 +29,13 @@ namespace Content.Client.Voting.UI
|
||||
|
||||
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
||||
|
||||
if (_vote.TargetEntity != null && _vote.TargetEntity != 0)
|
||||
{
|
||||
_targetEntity = new NetEntity(_vote.TargetEntity.Value);
|
||||
FollowVoteTarget.Visible = true;
|
||||
FollowVoteTarget.OnPressed += _ => AttemptFollowVoteEntity();
|
||||
}
|
||||
|
||||
Modulate = Color.White.WithAlpha(0.75f);
|
||||
_voteButtons = new Button[vote.Entries.Length];
|
||||
var group = new ButtonGroup();
|
||||
@@ -55,13 +62,29 @@ namespace Content.Client.Voting.UI
|
||||
for (var i = 0; i < _voteButtons.Length; i++)
|
||||
{
|
||||
var entry = _vote.Entries[i];
|
||||
_voteButtons[i].Text = Loc.GetString("ui-vote-button", ("text", entry.Text), ("votes", entry.Votes));
|
||||
if (_vote.DisplayVotes)
|
||||
{
|
||||
_voteButtons[i].Text = Loc.GetString("ui-vote-button", ("text", entry.Text), ("votes", entry.Votes));
|
||||
}
|
||||
else
|
||||
{
|
||||
_voteButtons[i].Text = Loc.GetString("ui-vote-button-no-votes", ("text", entry.Text));
|
||||
}
|
||||
|
||||
if (_vote.OurVote == i)
|
||||
_voteButtons[i].Pressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void AttemptFollowVoteEntity()
|
||||
{
|
||||
if (_targetEntity != null)
|
||||
{
|
||||
var msg = new GhostWarpToTargetRequestEvent(_targetEntity.Value);
|
||||
_net.SendSystemNetworkMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
// Logger.Debug($"{_gameTiming.ServerTime}, {_vote.StartTime}, {_vote.EndTime}");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Voting;
|
||||
@@ -184,6 +184,8 @@ namespace Content.Client.Voting
|
||||
existingVote.Title = message.VoteTitle;
|
||||
existingVote.StartTime = _gameTiming.RealServerToLocal(message.StartTime);
|
||||
existingVote.EndTime = _gameTiming.RealServerToLocal(message.EndTime);
|
||||
existingVote.DisplayVotes = message.DisplayVotes;
|
||||
existingVote.TargetEntity = message.TargetEntity;
|
||||
|
||||
// Logger.Debug($"{existingVote.StartTime}, {existingVote.EndTime}, {_gameTiming.RealTime}");
|
||||
|
||||
@@ -245,7 +247,8 @@ namespace Content.Client.Voting
|
||||
public string Initiator = "";
|
||||
public int? OurVote;
|
||||
public int Id;
|
||||
|
||||
public bool DisplayVotes;
|
||||
public int? TargetEntity; // NetEntity
|
||||
public ActiveVote(int voteId)
|
||||
{
|
||||
Id = voteId;
|
||||
|
||||
34
Content.Client/Voting/VotingSystem.cs
Normal file
34
Content.Client/Voting/VotingSystem.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Content.Client.Ghost;
|
||||
using Content.Shared.Voting;
|
||||
|
||||
namespace Content.Client.Voting;
|
||||
|
||||
public sealed class VotingSystem : EntitySystem
|
||||
{
|
||||
|
||||
public event Action<VotePlayerListResponseEvent>? VotePlayerListResponse; //Provides a list of players elligble for vote actions
|
||||
|
||||
[Dependency] private readonly GhostSystem _ghostSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<VotePlayerListResponseEvent>(OnVotePlayerListResponseEvent);
|
||||
}
|
||||
|
||||
private void OnVotePlayerListResponseEvent(VotePlayerListResponseEvent msg)
|
||||
{
|
||||
if (!_ghostSystem.IsGhost)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VotePlayerListResponse?.Invoke(msg);
|
||||
}
|
||||
|
||||
public void RequestVotePlayerList()
|
||||
{
|
||||
RaiseNetworkEvent(new VotePlayerListRequestEvent());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user