Add Votekick functionality (#32005)

This commit is contained in:
SlamBamActionman
2024-09-26 18:32:13 +02:00
committed by GitHub
parent 4491550a5e
commit eeadc75b0a
20 changed files with 1009 additions and 164 deletions

View File

@@ -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'}" />

View File

@@ -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;
}
}
}

View File

@@ -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" />

View File

@@ -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}");

View File

@@ -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;

View 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());
}
}