Add Votekick functionality (#32005)
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Database;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.GameTicking.Presets;
|
||||
using Content.Server.Maps;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Players;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Voting;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
@@ -16,20 +23,33 @@ namespace Content.Server.Voting.Managers
|
||||
{
|
||||
public sealed partial class VoteManager
|
||||
{
|
||||
[Dependency] private readonly IPlayerLocator _locator = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IBanManager _bans = default!;
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
|
||||
private VotingSystem? _votingSystem;
|
||||
private RoleSystem? _roleSystem;
|
||||
|
||||
private static readonly Dictionary<StandardVoteType, CVarDef<bool>> _voteTypesToEnableCVars = new()
|
||||
{
|
||||
{StandardVoteType.Restart, CCVars.VoteRestartEnabled},
|
||||
{StandardVoteType.Preset, CCVars.VotePresetEnabled},
|
||||
{StandardVoteType.Map, CCVars.VoteMapEnabled},
|
||||
{StandardVoteType.Votekick, CCVars.VotekickEnabled}
|
||||
};
|
||||
|
||||
public void CreateStandardVote(ICommonSession? initiator, StandardVoteType voteType)
|
||||
public void CreateStandardVote(ICommonSession? initiator, StandardVoteType voteType, string[]? args = null)
|
||||
{
|
||||
if (initiator != null)
|
||||
if (initiator != null && args == null)
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Medium, $"{initiator} initiated a {voteType.ToString()} vote");
|
||||
else if (initiator != null && args != null)
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"{initiator} initiated a {voteType.ToString()} vote with the arguments: {String.Join(",", args)}");
|
||||
else
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Medium, $"Initiated a {voteType.ToString()} vote");
|
||||
|
||||
bool timeoutVote = true;
|
||||
|
||||
switch (voteType)
|
||||
{
|
||||
case StandardVoteType.Restart:
|
||||
@@ -41,12 +61,17 @@ namespace Content.Server.Voting.Managers
|
||||
case StandardVoteType.Map:
|
||||
CreateMapVote(initiator);
|
||||
break;
|
||||
case StandardVoteType.Votekick:
|
||||
timeoutVote = false; // Allows the timeout to be updated manually in the create method
|
||||
CreateVotekickVote(initiator, args);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(voteType), voteType, null);
|
||||
}
|
||||
var ticker = _entityManager.EntitySysManager.GetEntitySystem<GameTicker>();
|
||||
ticker.UpdateInfoText();
|
||||
TimeoutStandardVote(voteType);
|
||||
if (timeoutVote)
|
||||
TimeoutStandardVote(voteType);
|
||||
}
|
||||
|
||||
private void CreateRestartVote(ICommonSession? initiator)
|
||||
@@ -56,104 +81,127 @@ namespace Content.Server.Voting.Managers
|
||||
var totalPlayers = _playerManager.Sessions.Count(session => session.Status != SessionStatus.Disconnected);
|
||||
|
||||
var ghostVotePercentageRequirement = _cfg.GetCVar(CCVars.VoteRestartGhostPercentage);
|
||||
var ghostCount = 0;
|
||||
|
||||
foreach (var player in _playerManager.Sessions)
|
||||
{
|
||||
_playerManager.UpdateState(player);
|
||||
if (player.Status != SessionStatus.Disconnected && _entityManager.HasComponent<GhostComponent>(player.AttachedEntity))
|
||||
{
|
||||
ghostCount++;
|
||||
}
|
||||
}
|
||||
var ghostVoterPercentage = CalculateEligibleVoterPercentage(VoterEligibility.Ghost);
|
||||
|
||||
var ghostPercentage = 0.0;
|
||||
if (totalPlayers > 0)
|
||||
{
|
||||
ghostPercentage = ((double)ghostCount / totalPlayers) * 100;
|
||||
}
|
||||
|
||||
var roundedGhostPercentage = (int)Math.Round(ghostPercentage);
|
||||
|
||||
if (totalPlayers <= playerVoteMaximum || roundedGhostPercentage >= ghostVotePercentageRequirement)
|
||||
if (totalPlayers <= playerVoteMaximum || ghostVoterPercentage >= ghostVotePercentageRequirement)
|
||||
{
|
||||
StartVote(initiator);
|
||||
}
|
||||
else
|
||||
{
|
||||
NotifyNotEnoughGhostPlayers(ghostVotePercentageRequirement, roundedGhostPercentage);
|
||||
NotifyNotEnoughGhostPlayers(ghostVotePercentageRequirement, ghostVoterPercentage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gives the current percentage of players eligible to vote, rounded to nearest percentage point.
|
||||
/// </summary>
|
||||
/// <param name="eligibility">The eligibility requirement to vote.</param>
|
||||
public int CalculateEligibleVoterPercentage(VoterEligibility eligibility)
|
||||
{
|
||||
var eligibleCount = CalculateEligibleVoterNumber(eligibility);
|
||||
var totalPlayers = _playerManager.Sessions.Count(session => session.Status != SessionStatus.Disconnected);
|
||||
|
||||
var eligiblePercentage = 0.0;
|
||||
if (totalPlayers > 0)
|
||||
{
|
||||
eligiblePercentage = ((double)eligibleCount / totalPlayers) * 100;
|
||||
}
|
||||
|
||||
var roundedEligiblePercentage = (int)Math.Round(eligiblePercentage);
|
||||
|
||||
return roundedEligiblePercentage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gives the current number of players eligible to vote.
|
||||
/// </summary>
|
||||
/// <param name="eligibility">The eligibility requirement to vote.</param>
|
||||
public int CalculateEligibleVoterNumber(VoterEligibility eligibility)
|
||||
{
|
||||
var eligibleCount = 0;
|
||||
|
||||
foreach (var player in _playerManager.Sessions)
|
||||
{
|
||||
_playerManager.UpdateState(player);
|
||||
if (player.Status != SessionStatus.Disconnected && CheckVoterEligibility(player, eligibility))
|
||||
{
|
||||
eligibleCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return eligibleCount;
|
||||
}
|
||||
|
||||
private void StartVote(ICommonSession? initiator)
|
||||
{
|
||||
var alone = _playerManager.PlayerCount == 1 && initiator != null;
|
||||
var options = new VoteOptions
|
||||
var options = new VoteOptions
|
||||
{
|
||||
Title = Loc.GetString("ui-vote-restart-title"),
|
||||
Options =
|
||||
{
|
||||
Title = Loc.GetString("ui-vote-restart-title"),
|
||||
Options =
|
||||
{
|
||||
(Loc.GetString("ui-vote-restart-yes"), "yes"),
|
||||
(Loc.GetString("ui-vote-restart-no"), "no"),
|
||||
(Loc.GetString("ui-vote-restart-abstain"), "abstain")
|
||||
},
|
||||
Duration = alone
|
||||
? TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.VoteTimerAlone))
|
||||
: TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.VoteTimerRestart)),
|
||||
InitiatorTimeout = TimeSpan.FromMinutes(5)
|
||||
};
|
||||
(Loc.GetString("ui-vote-restart-yes"), "yes"),
|
||||
(Loc.GetString("ui-vote-restart-no"), "no"),
|
||||
(Loc.GetString("ui-vote-restart-abstain"), "abstain")
|
||||
},
|
||||
Duration = alone
|
||||
? TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.VoteTimerAlone))
|
||||
: TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.VoteTimerRestart)),
|
||||
InitiatorTimeout = TimeSpan.FromMinutes(5)
|
||||
};
|
||||
|
||||
if (alone)
|
||||
options.InitiatorTimeout = TimeSpan.FromSeconds(10);
|
||||
if (alone)
|
||||
options.InitiatorTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
WirePresetVoteInitiator(options, initiator);
|
||||
WirePresetVoteInitiator(options, initiator);
|
||||
|
||||
var vote = CreateVote(options);
|
||||
var vote = CreateVote(options);
|
||||
|
||||
vote.OnFinished += (_, _) =>
|
||||
vote.OnFinished += (_, _) =>
|
||||
{
|
||||
var votesYes = vote.VotesPerOption["yes"];
|
||||
var votesNo = vote.VotesPerOption["no"];
|
||||
var total = votesYes + votesNo;
|
||||
|
||||
var ratioRequired = _cfg.GetCVar(CCVars.VoteRestartRequiredRatio);
|
||||
if (total > 0 && votesYes / (float) total >= ratioRequired)
|
||||
{
|
||||
var votesYes = vote.VotesPerOption["yes"];
|
||||
var votesNo = vote.VotesPerOption["no"];
|
||||
var total = votesYes + votesNo;
|
||||
|
||||
var ratioRequired = _cfg.GetCVar(CCVars.VoteRestartRequiredRatio);
|
||||
if (total > 0 && votesYes / (float) total >= ratioRequired)
|
||||
// Check if an admin is online, and ignore the passed vote if the cvar is enabled
|
||||
if (_cfg.GetCVar(CCVars.VoteRestartNotAllowedWhenAdminOnline) && _adminMgr.ActiveAdmins.Count() != 0)
|
||||
{
|
||||
// Check if an admin is online, and ignore the passed vote if the cvar is enabled
|
||||
if (_cfg.GetCVar(CCVars.VoteRestartNotAllowedWhenAdminOnline) && _adminMgr.ActiveAdmins.Count() != 0)
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Medium, $"Restart vote attempted to pass, but an admin was online. {votesYes}/{votesNo}");
|
||||
}
|
||||
else // If the cvar is disabled or there's no admins on, proceed as normal
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Medium, $"Restart vote succeeded: {votesYes}/{votesNo}");
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("ui-vote-restart-succeeded"));
|
||||
var roundEnd = _entityManager.EntitySysManager.GetEntitySystem<RoundEndSystem>();
|
||||
roundEnd.EndRound();
|
||||
}
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Medium, $"Restart vote attempted to pass, but an admin was online. {votesYes}/{votesNo}");
|
||||
}
|
||||
else
|
||||
else // If the cvar is disabled or there's no admins on, proceed as normal
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Medium, $"Restart vote failed: {votesYes}/{votesNo}");
|
||||
_chatManager.DispatchServerAnnouncement(
|
||||
Loc.GetString("ui-vote-restart-failed", ("ratio", ratioRequired)));
|
||||
}
|
||||
};
|
||||
|
||||
if (initiator != null)
|
||||
{
|
||||
// Cast yes vote if created the vote yourself.
|
||||
vote.CastVote(initiator, 0);
|
||||
}
|
||||
|
||||
foreach (var player in _playerManager.Sessions)
|
||||
{
|
||||
if (player != initiator)
|
||||
{
|
||||
// Everybody else defaults to an abstain vote to say they don't mind.
|
||||
vote.CastVote(player, 2);
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Medium, $"Restart vote succeeded: {votesYes}/{votesNo}");
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("ui-vote-restart-succeeded"));
|
||||
var roundEnd = _entityManager.EntitySysManager.GetEntitySystem<RoundEndSystem>();
|
||||
roundEnd.EndRound();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Medium, $"Restart vote failed: {votesYes}/{votesNo}");
|
||||
_chatManager.DispatchServerAnnouncement(
|
||||
Loc.GetString("ui-vote-restart-failed", ("ratio", ratioRequired)));
|
||||
}
|
||||
};
|
||||
|
||||
if (initiator != null)
|
||||
{
|
||||
// Cast yes vote if created the vote yourself.
|
||||
vote.CastVote(initiator, 0);
|
||||
}
|
||||
|
||||
foreach (var player in _playerManager.Sessions)
|
||||
{
|
||||
if (player != initiator)
|
||||
{
|
||||
// Everybody else defaults to an abstain vote to say they don't mind.
|
||||
vote.CastVote(player, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyNotEnoughGhostPlayers(int ghostPercentageRequirement, int roundedGhostPercentage)
|
||||
@@ -275,6 +323,230 @@ namespace Content.Server.Voting.Managers
|
||||
};
|
||||
}
|
||||
|
||||
private async void CreateVotekickVote(ICommonSession? initiator, string[]? args)
|
||||
{
|
||||
if (args == null || args.Length <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_roleSystem == null)
|
||||
_roleSystem = _entityManager.SystemOrNull<RoleSystem>();
|
||||
if (_votingSystem == null)
|
||||
_votingSystem = _entityManager.SystemOrNull<VotingSystem>();
|
||||
|
||||
// Check that the initiator is actually allowed to do a votekick.
|
||||
if (_votingSystem != null && !await _votingSystem.CheckVotekickInitEligibility(initiator))
|
||||
{
|
||||
_logManager.GetSawmill("admin.votekick").Warning($"User {initiator} attempted a votekick, despite not being eligible to!");
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick attempted by {initiator}, but they are not eligible to votekick!");
|
||||
DirtyCanCallVoteAll();
|
||||
return;
|
||||
}
|
||||
|
||||
var eligibleVoterNumberRequirement = _cfg.GetCVar(CCVars.VotekickEligibleNumberRequirement);
|
||||
var eligibleVoterNumber = _cfg.GetCVar(CCVars.VotekickVoterGhostRequirement) ? CalculateEligibleVoterNumber(VoterEligibility.GhostMinimumPlaytime) : CalculateEligibleVoterNumber(VoterEligibility.MinimumPlaytime);
|
||||
|
||||
string target = args[0];
|
||||
string reason = args[1];
|
||||
|
||||
// Start by getting all relevant target data
|
||||
var located = await _locator.LookupIdByNameOrIdAsync(target);
|
||||
if (located == null)
|
||||
{
|
||||
_logManager.GetSawmill("admin.votekick")
|
||||
.Warning($"Votekick attempted for player {target} but they couldn't be found!");
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick attempted by {initiator} for player string {target}, but they could not be found!");
|
||||
DirtyCanCallVoteAll();
|
||||
return;
|
||||
}
|
||||
var targetUid = located.UserId;
|
||||
var targetHWid = located.LastHWId;
|
||||
if (!_playerManager.TryGetSessionById(located.UserId, out ICommonSession? targetSession))
|
||||
{
|
||||
_logManager.GetSawmill("admin.votekick")
|
||||
.Warning($"Votekick attempted for player {target} but their session couldn't be found!");
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick attempted by {initiator} for player string {target}, but they could not be found!");
|
||||
DirtyCanCallVoteAll();
|
||||
return;
|
||||
}
|
||||
|
||||
string targetEntityName = located.Username; // Target's player-facing name when voting; uses the player's username as fallback if no entity name is found
|
||||
if (targetSession.AttachedEntity is { Valid: true } attached && _votingSystem != null)
|
||||
targetEntityName = _votingSystem.GetPlayerVoteListName(attached);
|
||||
|
||||
var isAntagSafe = false;
|
||||
var targetMind = targetSession.GetMind();
|
||||
var playtime = _playtimeManager.GetPlayTimes(targetSession);
|
||||
|
||||
// Check whether the target is an antag, and if they are, give them protection against the Raider votekick if they have the requisite hours.
|
||||
if (targetMind != null &&
|
||||
_roleSystem != null &&
|
||||
_roleSystem.MindIsAntagonist(targetMind) &&
|
||||
playtime.TryGetValue(PlayTimeTrackingShared.TrackerOverall, out TimeSpan overallTime) &&
|
||||
overallTime >= TimeSpan.FromHours(_cfg.GetCVar(CCVars.VotekickAntagRaiderProtection)))
|
||||
{
|
||||
isAntagSafe = true;
|
||||
}
|
||||
|
||||
|
||||
// Don't let a user votekick themselves
|
||||
if (initiator == targetSession)
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick attempted by {initiator} for themselves? Votekick cancelled.");
|
||||
DirtyCanCallVoteAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancels the vote if there's not enough voters; only the person initiating the vote gets a return message.
|
||||
if (eligibleVoterNumber < eligibleVoterNumberRequirement)
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick attempted by {initiator} for player {targetSession}, but there were not enough ghost roles! {eligibleVoterNumberRequirement} required, {eligibleVoterNumber} found.");
|
||||
if (initiator != null)
|
||||
{
|
||||
var message = Loc.GetString("ui-vote-votekick-not-enough-eligible", ("voters", eligibleVoterNumber.ToString()), ("requirement", eligibleVoterNumberRequirement.ToString()));
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, default, false, initiator.Channel);
|
||||
}
|
||||
DirtyCanCallVoteAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for stuff like the target being an admin. These targets shouldn't show up in the UI, but it's necessary to doublecheck in case someone writes the command in console.
|
||||
if (_votingSystem != null && !_votingSystem.CheckVotekickTargetEligibility(targetSession))
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick attempted by {initiator} for player {targetSession}, but they are not eligible to be votekicked!");
|
||||
DirtyCanCallVoteAll();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the vote object
|
||||
|
||||
string voteTitle = "";
|
||||
NetEntity? targetNetEntity = _entityManager.GetNetEntity(targetSession.AttachedEntity);
|
||||
var initiatorName = initiator != null ? initiator.Name : Loc.GetString("ui-vote-votekick-unknown-initiator");
|
||||
|
||||
voteTitle = Loc.GetString("ui-vote-votekick-title", ("initiator", initiatorName), ("targetEntity", targetEntityName), ("reason", reason));
|
||||
|
||||
var options = new VoteOptions
|
||||
{
|
||||
Title = voteTitle,
|
||||
Options =
|
||||
{
|
||||
(Loc.GetString("ui-vote-votekick-yes"), "yes"),
|
||||
(Loc.GetString("ui-vote-votekick-no"), "no"),
|
||||
(Loc.GetString("ui-vote-votekick-abstain"), "abstain")
|
||||
},
|
||||
Duration = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.VotekickTimer)),
|
||||
InitiatorTimeout = TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.VotekickTimeout)),
|
||||
VoterEligibility = _cfg.GetCVar(CCVars.VotekickVoterGhostRequirement) ? VoterEligibility.GhostMinimumPlaytime : VoterEligibility.MinimumPlaytime,
|
||||
DisplayVotes = false,
|
||||
TargetEntity = targetNetEntity
|
||||
};
|
||||
|
||||
WirePresetVoteInitiator(options, initiator);
|
||||
|
||||
var vote = CreateVote(options);
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} ({targetEntityName}) due to {reason} started, initiated by {initiator}.");
|
||||
|
||||
// Time out the vote now that we know it will happen
|
||||
TimeoutStandardVote(StandardVoteType.Votekick);
|
||||
|
||||
vote.OnFinished += (_, _) =>
|
||||
{
|
||||
|
||||
var votesYes = vote.VotesPerOption["yes"];
|
||||
var votesNo = vote.VotesPerOption["no"];
|
||||
var total = votesYes + votesNo;
|
||||
|
||||
// Get the voters, for logging purposes.
|
||||
List<ICommonSession> yesVoters = new();
|
||||
List<ICommonSession> noVoters = new();
|
||||
foreach (var (voter, castVote) in vote.CastVotes)
|
||||
{
|
||||
if (castVote == 0)
|
||||
{
|
||||
yesVoters.Add(voter);
|
||||
}
|
||||
if (castVote == 1)
|
||||
{
|
||||
noVoters.Add(voter);
|
||||
}
|
||||
}
|
||||
var yesVotersString = string.Join(", ", yesVoters);
|
||||
var noVotersString = string.Join(", ", noVoters);
|
||||
|
||||
var ratioRequired = _cfg.GetCVar(CCVars.VotekickRequiredRatio);
|
||||
if (total > 0 && votesYes / (float)total >= ratioRequired)
|
||||
{
|
||||
// Some conditions that cancel the vote want to let the vote run its course first and then cancel it
|
||||
// so we check for that here
|
||||
|
||||
// Check if an admin is online, and ignore the vote if the cvar is enabled
|
||||
if (_cfg.GetCVar(CCVars.VotekickNotAllowedWhenAdminOnline) && _adminMgr.ActiveAdmins.Count() != 0)
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} attempted to pass, but an admin was online. Yes: {votesYes} / No: {votesNo}. Yes: {yesVotersString} / No: {noVotersString}");
|
||||
AnnounceCancelledVotekickForVoters(targetEntityName);
|
||||
return;
|
||||
}
|
||||
// Check if the target is an antag and the vote reason is raiding (this is to prevent false positives)
|
||||
else if (isAntagSafe && reason == VotekickReasonType.Raiding.ToString())
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} due to {reason} finished, created by {initiator}, but was cancelled due to the target being an antagonist.");
|
||||
AnnounceCancelledVotekickForVoters(targetEntityName);
|
||||
return;
|
||||
}
|
||||
// Check if the target is an admin/de-admined admin
|
||||
else if (targetSession.AttachedEntity != null && _adminMgr.IsAdmin(targetSession.AttachedEntity.Value, true))
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} due to {reason} finished, created by {initiator}, but was cancelled due to the target being a de-admined admin.");
|
||||
AnnounceCancelledVotekickForVoters(targetEntityName);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick for {located.Username} succeeded: Yes: {votesYes} / No: {votesNo}. Yes: {yesVotersString} / No: {noVotersString}");
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("ui-vote-votekick-success", ("target", targetEntityName), ("reason", reason)));
|
||||
|
||||
if (!Enum.TryParse(_cfg.GetCVar(CCVars.VotekickBanDefaultSeverity), out NoteSeverity severity))
|
||||
{
|
||||
_logManager.GetSawmill("admin.votekick")
|
||||
.Warning("Votekick ban severity could not be parsed from config! Defaulting to high.");
|
||||
severity = NoteSeverity.High;
|
||||
}
|
||||
|
||||
uint minutes = (uint)_cfg.GetCVar(CCVars.VotekickBanDuration);
|
||||
|
||||
_bans.CreateServerBan(targetUid, target, null, null, targetHWid, minutes, severity, reason);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_adminLogger.Add(LogType.Vote, LogImpact.Extreme, $"Votekick failed: Yes: {votesYes} / No: {votesNo}. Yes: {yesVotersString} / No: {noVotersString}");
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("ui-vote-votekick-failure", ("target", targetEntityName), ("reason", reason)));
|
||||
}
|
||||
};
|
||||
|
||||
if (initiator != null)
|
||||
{
|
||||
// Cast yes vote if created the vote yourself.
|
||||
vote.CastVote(initiator, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void AnnounceCancelledVotekickForVoters(string target)
|
||||
{
|
||||
foreach (var player in _playerManager.Sessions)
|
||||
{
|
||||
if (CheckVoterEligibility(player, VoterEligibility.GhostMinimumPlaytime))
|
||||
{
|
||||
var message = Loc.GetString("ui-vote-votekick-server-cancelled", ("target", target));
|
||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
|
||||
_chatManager.ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, default, false, player.Channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TimeoutStandardVote(StandardVoteType type)
|
||||
{
|
||||
var timeout = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.VoteSameTypeTimeout));
|
||||
|
||||
Reference in New Issue
Block a user