2021-11-20 12:32:07 -06:00
using System.Linq ;
2021-06-20 10:09:24 +02:00
using Content.Server.GameTicking ;
2021-12-21 21:23:29 +01:00
using Content.Server.GameTicking.Presets ;
2021-11-20 12:32:07 -06:00
using Content.Server.Maps ;
2021-10-09 13:18:20 -05:00
using Content.Server.RoundEnd ;
2021-06-13 14:52:40 +02:00
using Content.Shared.CCVar ;
2023-02-16 18:29:44 -06:00
using Content.Shared.Database ;
2024-01-19 20:10:05 +13:00
using Content.Shared.Ghost ;
2021-07-21 19:03:10 +02:00
using Content.Shared.Voting ;
2021-11-23 17:03:04 +00:00
using Robust.Shared.Configuration ;
2024-01-19 20:10:05 +13:00
using Robust.Shared.Enums ;
2023-10-28 09:59:53 +11:00
using Robust.Shared.Player ;
2021-02-16 15:07:17 +01:00
using Robust.Shared.Random ;
2021-06-09 22:19:39 +02:00
namespace Content.Server.Voting.Managers
2021-02-16 15:07:17 +01:00
{
public sealed partial class VoteManager
{
2021-11-23 17:03:04 +00:00
private static readonly Dictionary < StandardVoteType , CVarDef < bool > > _voteTypesToEnableCVars = new ( )
{
{ StandardVoteType . Restart , CCVars . VoteRestartEnabled } ,
{ StandardVoteType . Preset , CCVars . VotePresetEnabled } ,
{ StandardVoteType . Map , CCVars . VoteMapEnabled } ,
} ;
2023-10-28 09:59:53 +11:00
public void CreateStandardVote ( ICommonSession ? initiator , StandardVoteType voteType )
2021-07-21 19:03:10 +02:00
{
2023-02-16 18:29:44 -06:00
if ( initiator ! = null )
_adminLogger . Add ( LogType . Vote , LogImpact . Medium , $"{initiator} initiated a {voteType.ToString()} vote" ) ;
else
_adminLogger . Add ( LogType . Vote , LogImpact . Medium , $"Initiated a {voteType.ToString()} vote" ) ;
2021-07-21 19:03:10 +02:00
switch ( voteType )
{
case StandardVoteType . Restart :
CreateRestartVote ( initiator ) ;
break ;
case StandardVoteType . Preset :
CreatePresetVote ( initiator ) ;
break ;
2021-11-11 23:25:57 -07:00
case StandardVoteType . Map :
CreateMapVote ( initiator ) ;
break ;
2021-07-21 19:03:10 +02:00
default :
throw new ArgumentOutOfRangeException ( nameof ( voteType ) , voteType , null ) ;
}
2022-11-07 18:18:21 -08:00
var ticker = _entityManager . EntitySysManager . GetEntitySystem < GameTicker > ( ) ;
2022-04-15 13:55:38 -05:00
ticker . UpdateInfoText ( ) ;
2021-07-21 19:03:10 +02:00
TimeoutStandardVote ( voteType ) ;
}
2023-10-28 09:59:53 +11:00
private void CreateRestartVote ( ICommonSession ? initiator )
2021-02-16 15:07:17 +01:00
{
2024-01-19 20:10:05 +13:00
var playerVoteMaximum = _cfg . GetCVar ( CCVars . VoteRestartMaxPlayers ) ;
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 )
2021-02-16 15:07:17 +01:00
{
2024-01-19 20:10:05 +13:00
_playerManager . UpdateState ( player ) ;
if ( player . Status ! = SessionStatus . Disconnected & & _entityManager . HasComponent < GhostComponent > ( player . AttachedEntity ) )
2021-02-16 15:07:17 +01:00
{
2024-01-19 20:10:05 +13:00
ghostCount + + ;
}
}
2021-02-16 15:07:17 +01:00
2024-01-19 20:10:05 +13:00
var ghostPercentage = 0.0 ;
if ( totalPlayers > 0 )
{
ghostPercentage = ( ( double ) ghostCount / totalPlayers ) * 100 ;
}
2021-02-16 15:07:17 +01:00
2024-01-19 20:10:05 +13:00
var roundedGhostPercentage = ( int ) Math . Round ( ghostPercentage ) ;
2021-02-16 15:07:17 +01:00
2024-01-19 20:10:05 +13:00
if ( totalPlayers < = playerVoteMaximum | | roundedGhostPercentage > = ghostVotePercentageRequirement )
2021-02-16 15:07:17 +01:00
{
2024-01-19 20:10:05 +13:00
StartVote ( initiator ) ;
}
else
{
NotifyNotEnoughGhostPlayers ( ghostVotePercentageRequirement , roundedGhostPercentage ) ;
}
}
2021-02-16 15:07:17 +01:00
2024-01-19 20:10:05 +13:00
private void StartVote ( ICommonSession ? initiator )
{
var alone = _playerManager . PlayerCount = = 1 & & initiator ! = null ;
var options = new VoteOptions
2021-02-16 15:07:17 +01:00
{
2024-01-19 20:10:05 +13:00
Title = Loc . GetString ( "ui-vote-restart-title" ) ,
Options =
2023-12-24 23:55:56 -08:00
{
2024-01-19 20:10:05 +13:00
( 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 ) ;
WirePresetVoteInitiator ( options , initiator ) ;
var vote = CreateVote ( options ) ;
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 )
2023-12-24 23:55:56 -08:00
{
2024-01-19 20:10:05 +13:00
// 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 ( ) ;
}
2023-12-24 23:55:56 -08:00
}
2024-01-19 20:10:05 +13:00
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 )
2021-02-16 15:07:17 +01:00
{
2024-01-19 20:10:05 +13:00
// Cast yes vote if created the vote yourself.
vote . CastVote ( initiator , 0 ) ;
2021-02-16 15:07:17 +01:00
}
2024-01-19 20:10:05 +13:00
foreach ( var player in _playerManager . Sessions )
2021-07-21 15:24:43 +02:00
{
2024-01-19 20:10:05 +13:00
if ( player ! = initiator )
{
// Everybody else defaults to an abstain vote to say they don't mind.
vote . CastVote ( player , 2 ) ;
}
2021-07-21 15:24:43 +02:00
}
2024-01-19 20:10:05 +13:00
}
private void NotifyNotEnoughGhostPlayers ( int ghostPercentageRequirement , int roundedGhostPercentage )
{
// Logic to notify that there are not enough ghost players to start a vote
_adminLogger . Add ( LogType . Vote , LogImpact . Medium , $"Restart vote failed: Current Ghost player percentage:{roundedGhostPercentage.ToString()}% does not meet {ghostPercentageRequirement.ToString()}%" ) ;
_chatManager . DispatchServerAnnouncement (
Loc . GetString ( "ui-vote-restart-fail-not-enough-ghost-players" , ( "ghostPlayerRequirement" , ghostPercentageRequirement ) ) ) ;
2021-02-16 15:07:17 +01:00
}
2023-10-28 09:59:53 +11:00
private void CreatePresetVote ( ICommonSession ? initiator )
2021-02-16 15:07:17 +01:00
{
2022-10-15 06:10:10 +02:00
var presets = GetGamePresets ( ) ;
2021-02-16 15:07:17 +01:00
var alone = _playerManager . PlayerCount = = 1 & & initiator ! = null ;
var options = new VoteOptions
{
2021-02-28 22:11:45 +01:00
Title = Loc . GetString ( "ui-vote-gamemode-title" ) ,
2021-02-16 15:07:17 +01:00
Duration = alone
2022-02-06 21:26:19 -08:00
? TimeSpan . FromSeconds ( _cfg . GetCVar ( CCVars . VoteTimerAlone ) )
: TimeSpan . FromSeconds ( _cfg . GetCVar ( CCVars . VoteTimerPreset ) )
2021-02-16 15:07:17 +01:00
} ;
if ( alone )
options . InitiatorTimeout = TimeSpan . FromSeconds ( 10 ) ;
foreach ( var ( k , v ) in presets )
{
options . Options . Add ( ( Loc . GetString ( v ) , k ) ) ;
}
WirePresetVoteInitiator ( options , initiator ) ;
var vote = CreateVote ( options ) ;
vote . OnFinished + = ( _ , args ) = >
{
string picked ;
if ( args . Winner = = null )
{
2021-07-21 15:15:12 +02:00
picked = ( string ) _random . Pick ( args . Winners ) ;
2021-02-16 15:07:17 +01:00
_chatManager . DispatchServerAnnouncement (
2021-02-28 22:11:45 +01:00
Loc . GetString ( "ui-vote-gamemode-tie" , ( "picked" , Loc . GetString ( presets [ picked ] ) ) ) ) ;
2021-02-16 15:07:17 +01:00
}
else
{
picked = ( string ) args . Winner ;
_chatManager . DispatchServerAnnouncement (
2021-02-28 22:11:45 +01:00
Loc . GetString ( "ui-vote-gamemode-win" , ( "winner" , Loc . GetString ( presets [ picked ] ) ) ) ) ;
2021-02-16 15:07:17 +01:00
}
2023-02-16 18:29:44 -06:00
_adminLogger . Add ( LogType . Vote , LogImpact . Medium , $"Preset vote finished: {picked}" ) ;
2022-11-07 18:18:21 -08:00
var ticker = _entityManager . EntitySysManager . GetEntitySystem < GameTicker > ( ) ;
ticker . SetGamePreset ( picked ) ;
2021-02-16 15:07:17 +01:00
} ;
}
2021-07-21 19:03:10 +02:00
2023-10-28 09:59:53 +11:00
private void CreateMapVote ( ICommonSession ? initiator )
2021-11-11 23:25:57 -07:00
{
2021-11-20 12:32:07 -06:00
var maps = _gameMapManager . CurrentlyEligibleMaps ( ) . ToDictionary ( map = > map , map = > map . MapName ) ;
2021-11-11 23:25:57 -07:00
var alone = _playerManager . PlayerCount = = 1 & & initiator ! = null ;
var options = new VoteOptions
{
Title = Loc . GetString ( "ui-vote-map-title" ) ,
Duration = alone
2022-02-06 21:26:19 -08:00
? TimeSpan . FromSeconds ( _cfg . GetCVar ( CCVars . VoteTimerAlone ) )
: TimeSpan . FromSeconds ( _cfg . GetCVar ( CCVars . VoteTimerMap ) )
2021-11-11 23:25:57 -07:00
} ;
if ( alone )
options . InitiatorTimeout = TimeSpan . FromSeconds ( 10 ) ;
foreach ( var ( k , v ) in maps )
{
options . Options . Add ( ( v , k ) ) ;
}
WirePresetVoteInitiator ( options , initiator ) ;
var vote = CreateVote ( options ) ;
vote . OnFinished + = ( _ , args ) = >
{
2021-11-20 12:32:07 -06:00
GameMapPrototype picked ;
2021-11-11 23:25:57 -07:00
if ( args . Winner = = null )
{
2021-11-20 12:32:07 -06:00
picked = ( GameMapPrototype ) _random . Pick ( args . Winners ) ;
2021-11-11 23:25:57 -07:00
_chatManager . DispatchServerAnnouncement (
Loc . GetString ( "ui-vote-map-tie" , ( "picked" , maps [ picked ] ) ) ) ;
}
else
{
2021-11-20 12:32:07 -06:00
picked = ( GameMapPrototype ) args . Winner ;
2021-11-11 23:25:57 -07:00
_chatManager . DispatchServerAnnouncement (
Loc . GetString ( "ui-vote-map-win" , ( "winner" , maps [ picked ] ) ) ) ;
}
2023-02-16 18:29:44 -06:00
_adminLogger . Add ( LogType . Vote , LogImpact . Medium , $"Map vote finished: {picked.MapName}" ) ;
2022-11-07 18:18:21 -08:00
var ticker = _entityManager . EntitySysManager . GetEntitySystem < GameTicker > ( ) ;
2023-03-22 20:29:55 +11:00
if ( ticker . CanUpdateMap ( ) )
2022-11-07 18:18:21 -08:00
{
if ( _gameMapManager . TrySelectMapIfEligible ( picked . ID ) )
{
ticker . UpdateInfoText ( ) ;
}
}
else
{
2023-03-22 20:29:55 +11:00
if ( ticker . RoundPreloadTime < = TimeSpan . Zero )
{
_chatManager . DispatchServerAnnouncement ( Loc . GetString ( "ui-vote-map-notlobby" ) ) ;
}
else
{
2023-05-28 14:19:08 -04:00
var timeString = $"{ticker.RoundPreloadTime.Minutes:0}:{ticker.RoundPreloadTime.Seconds:00}" ;
_chatManager . DispatchServerAnnouncement ( Loc . GetString ( "ui-vote-map-notlobby-time" , ( "time" , timeString ) ) ) ;
2023-03-22 20:29:55 +11:00
}
2022-11-07 18:18:21 -08:00
}
2021-11-11 23:25:57 -07:00
} ;
}
2021-07-21 19:03:10 +02:00
private void TimeoutStandardVote ( StandardVoteType type )
{
var timeout = TimeSpan . FromSeconds ( _cfg . GetCVar ( CCVars . VoteSameTypeTimeout ) ) ;
_standardVoteTimeout [ type ] = _timing . RealTime + timeout ;
DirtyCanCallVoteAll ( ) ;
}
2022-10-15 06:10:10 +02:00
private Dictionary < string , string > GetGamePresets ( )
{
var presets = new Dictionary < string , string > ( ) ;
foreach ( var preset in _prototypeManager . EnumeratePrototypes < GamePresetPrototype > ( ) )
{
if ( ! preset . ShowInVote )
continue ;
if ( _playerManager . PlayerCount < ( preset . MinPlayers ? ? int . MinValue ) )
continue ;
if ( _playerManager . PlayerCount > ( preset . MaxPlayers ? ? int . MaxValue ) )
continue ;
presets [ preset . ID ] = preset . ModeTitle ;
}
return presets ;
}
2021-02-16 15:07:17 +01:00
}
}