Add delaystart and forcepreset commands (#1163)

* Add extendroundstart message to extend lobby start timer

* Rename StartExtend to DelayStart

* Fix delaystart amounts above 59 not working

* Change delaystart seconds type from int to uint

* Change delaystart wrong args amount message

* Add forcegamepreset command

* Rename forcegamepreset to forcepreset and merged start and forcestart preset methods

* Fix index out of bounds exception when forcing suspicion to start

* Change game preset to match regardless of casing

* Add forcepreset unknown preset message

* Add and move in lobby checks

* Remove testing changes

* Change delaystart to pause/resume the timer when no seconds are specified

* Change pause message

* Remove testing code

* Change 0 seconds to not be a valid amount of seconds

* Replace MsgTickerLobbyCountdown Seconds with DateTime instead of uint

* Add one entire dot

* Replace Math.Min + Math.Max with Math.Clamp

Co-authored-by: ComicIronic <comicironic@gmail.com>

Co-authored-by: ComicIronic <comicironic@gmail.com>
This commit is contained in:
DrSmugleaf
2020-06-21 22:05:47 +02:00
committed by GitHub
parent 7b98f37f9a
commit d91a8c4925
13 changed files with 274 additions and 37 deletions

View File

@@ -24,6 +24,7 @@ namespace Content.Client.GameTicking
[ViewVariables] public bool IsGameStarted { get; private set; } [ViewVariables] public bool IsGameStarted { get; private set; }
[ViewVariables] public string ServerInfoBlob { get; private set; } [ViewVariables] public string ServerInfoBlob { get; private set; }
[ViewVariables] public DateTime StartTime { get; private set; } [ViewVariables] public DateTime StartTime { get; private set; }
[ViewVariables] public bool Paused { get; private set; }
public event Action InfoBlobUpdated; public event Action InfoBlobUpdated;
public event Action LobbyStatusUpdated; public event Action LobbyStatusUpdated;
@@ -36,6 +37,7 @@ namespace Content.Client.GameTicking
_netManager.RegisterNetMessage<MsgTickerJoinGame>(nameof(MsgTickerJoinGame), JoinGame); _netManager.RegisterNetMessage<MsgTickerJoinGame>(nameof(MsgTickerJoinGame), JoinGame);
_netManager.RegisterNetMessage<MsgTickerLobbyStatus>(nameof(MsgTickerLobbyStatus), LobbyStatus); _netManager.RegisterNetMessage<MsgTickerLobbyStatus>(nameof(MsgTickerLobbyStatus), LobbyStatus);
_netManager.RegisterNetMessage<MsgTickerLobbyInfo>(nameof(MsgTickerLobbyInfo), LobbyInfo); _netManager.RegisterNetMessage<MsgTickerLobbyInfo>(nameof(MsgTickerLobbyInfo), LobbyInfo);
_netManager.RegisterNetMessage<MsgTickerLobbyCountdown>(nameof(MsgTickerLobbyCountdown), LobbyCountdown);
_netManager.RegisterNetMessage<MsgRoundEndMessage>(nameof(MsgRoundEndMessage), RoundEnd); _netManager.RegisterNetMessage<MsgRoundEndMessage>(nameof(MsgRoundEndMessage), RoundEnd);
_initialized = true; _initialized = true;
@@ -69,6 +71,12 @@ namespace Content.Client.GameTicking
_stateManager.RequestStateChange<GameScreen>(); _stateManager.RequestStateChange<GameScreen>();
} }
private void LobbyCountdown(MsgTickerLobbyCountdown message)
{
StartTime = message.StartTime;
Paused = message.Paused;
}
private void RoundEnd(MsgRoundEndMessage message) private void RoundEnd(MsgRoundEndMessage message)
{ {

View File

@@ -8,6 +8,7 @@ namespace Content.Client.Interfaces
string ServerInfoBlob { get; } string ServerInfoBlob { get; }
bool AreWeReady { get; } bool AreWeReady { get; }
DateTime StartTime { get; } DateTime StartTime { get; }
bool Paused { get; }
void Initialize(); void Initialize();
event Action InfoBlobUpdated; event Action InfoBlobUpdated;

View File

@@ -123,21 +123,29 @@ namespace Content.Client.State
} }
string text; string text;
var difference = _clientGameTicker.StartTime - DateTime.UtcNow;
if (difference.Ticks < 0) if (_clientGameTicker.Paused)
{ {
if (difference.TotalSeconds < -5) text = Loc.GetString("Paused");
{
text = Loc.GetString("Right Now?");
}
else
{
text = Loc.GetString("Right Now");
}
} }
else else
{ {
text = $"{(int) Math.Floor(difference.TotalMinutes)}:{difference.Seconds:D2}"; var difference = _clientGameTicker.StartTime - DateTime.UtcNow;
if (difference.Ticks < 0)
{
if (difference.TotalSeconds < -5)
{
text = Loc.GetString("Right Now?");
}
else
{
text = Loc.GetString("Right Now");
}
}
else
{
text = $"{(int) Math.Floor(difference.TotalMinutes)}:{difference.Seconds:D2}";
}
} }
_lobby.StartTime.Text = Loc.GetString("Round Starts In: {0}", text); _lobby.StartTime.Text = Loc.GetString("Round Starts In: {0}", text);

View File

@@ -37,7 +37,7 @@ namespace Content.IntegrationTests
{ {
} }
public void StartRound() public void StartRound(bool force = false)
{ {
} }
@@ -81,12 +81,33 @@ namespace Content.IntegrationTests
public IEnumerable<GameRule> ActiveGameRules { get; } = Array.Empty<GameRule>(); public IEnumerable<GameRule> ActiveGameRules { get; } = Array.Empty<GameRule>();
public void SetStartPreset(Type type) public bool TryGetPreset(string name, out Type type)
{
type = default;
return false;
}
public void SetStartPreset(Type type, bool force = false)
{ {
} }
public void SetStartPreset(string type) public void SetStartPreset(string name, bool force = false)
{ {
} }
public bool DelayStart(TimeSpan time)
{
return true;
}
public bool PauseStart(bool pause = true)
{
return true;
}
public bool TogglePause()
{
return false;
}
} }
} }

View File

@@ -8,7 +8,7 @@ namespace Content.Server.GameTicking
/// </summary> /// </summary>
public abstract class GamePreset public abstract class GamePreset
{ {
public abstract bool Start(IReadOnlyList<IPlayerSession> players); public abstract bool Start(IReadOnlyList<IPlayerSession> readyPlayers, bool force = false);
public virtual string ModeTitle => "Sandbox"; public virtual string ModeTitle => "Sandbox";
public virtual string Description => "Secret!"; public virtual string Description => "Secret!";
} }

View File

@@ -12,7 +12,7 @@ namespace Content.Server.GameTicking.GamePresets
[Dependency] private readonly IGameTicker _gameTicker; [Dependency] private readonly IGameTicker _gameTicker;
#pragma warning restore 649 #pragma warning restore 649
public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers) public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers, bool force = false)
{ {
_gameTicker.AddGameRule<RuleDeathMatch>(); _gameTicker.AddGameRule<RuleDeathMatch>();
return true; return true;

View File

@@ -11,7 +11,7 @@ namespace Content.Server.GameTicking.GamePresets
[Dependency] private readonly ISandboxManager _sandboxManager; [Dependency] private readonly ISandboxManager _sandboxManager;
#pragma warning restore 649 #pragma warning restore 649
public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers) public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers, bool force = false)
{ {
_sandboxManager.IsSandboxEnabled = true; _sandboxManager.IsSandboxEnabled = true;
return true; return true;

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Content.Server.GameTicking.GameRules; using Content.Server.GameTicking.GameRules;
using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameTicking; using Content.Server.Interfaces.GameTicking;
@@ -28,16 +27,17 @@ namespace Content.Server.GameTicking.GamePresets
public int MinTraitors { get; set; } = 2; public int MinTraitors { get; set; } = 2;
public int PlayersPerTraitor { get; set; } = 5; public int PlayersPerTraitor { get; set; } = 5;
public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers) public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers, bool force = false)
{ {
if (readyPlayers.Count < MinPlayers) if (!force && readyPlayers.Count < MinPlayers)
{ {
_chatManager.DispatchServerAnnouncement($"Not enough players readied up for the game! There were {readyPlayers.Count} players readied up out of {MinPlayers} needed."); _chatManager.DispatchServerAnnouncement($"Not enough players readied up for the game! There were {readyPlayers.Count} players readied up out of {MinPlayers} needed.");
return false; return false;
} }
var list = new List<IPlayerSession>(readyPlayers); var list = new List<IPlayerSession>(readyPlayers);
var numTraitors = Math.Max(readyPlayers.Count() % PlayersPerTraitor, MinTraitors); var numTraitors = Math.Clamp(readyPlayers.Count % PlayersPerTraitor,
MinTraitors, readyPlayers.Count);
for (var i = 0; i < numTraitors; i++) for (var i = 0; i < numTraitors; i++)
{ {

View File

@@ -81,6 +81,7 @@ namespace Content.Server.GameTicking
[ViewVariables] private Type _presetType; [ViewVariables] private Type _presetType;
[ViewVariables] private DateTime _pauseTime;
[ViewVariables] private bool _roundStartCountdownHasNotStartedYetDueToNoPlayers; [ViewVariables] private bool _roundStartCountdownHasNotStartedYetDueToNoPlayers;
private DateTime _roundStartTimeUtc; private DateTime _roundStartTimeUtc;
[ViewVariables] private GameRunLevel _runLevel; [ViewVariables] private GameRunLevel _runLevel;
@@ -92,6 +93,8 @@ namespace Content.Server.GameTicking
private CancellationTokenSource _updateShutdownCts; private CancellationTokenSource _updateShutdownCts;
[ViewVariables] public bool Paused { get; private set; }
[ViewVariables] [ViewVariables]
public GameRunLevel RunLevel public GameRunLevel RunLevel
{ {
@@ -128,6 +131,7 @@ namespace Content.Server.GameTicking
_netManager.RegisterNetMessage<MsgTickerJoinGame>(nameof(MsgTickerJoinGame)); _netManager.RegisterNetMessage<MsgTickerJoinGame>(nameof(MsgTickerJoinGame));
_netManager.RegisterNetMessage<MsgTickerLobbyStatus>(nameof(MsgTickerLobbyStatus)); _netManager.RegisterNetMessage<MsgTickerLobbyStatus>(nameof(MsgTickerLobbyStatus));
_netManager.RegisterNetMessage<MsgTickerLobbyInfo>(nameof(MsgTickerLobbyInfo)); _netManager.RegisterNetMessage<MsgTickerLobbyInfo>(nameof(MsgTickerLobbyInfo));
_netManager.RegisterNetMessage<MsgTickerLobbyCountdown>(nameof(MsgTickerLobbyCountdown));
_netManager.RegisterNetMessage<MsgRoundEndMessage>(nameof(MsgRoundEndMessage)); _netManager.RegisterNetMessage<MsgRoundEndMessage>(nameof(MsgRoundEndMessage));
SetStartPreset(_configurationManager.GetCVar<string>("game.defaultpreset")); SetStartPreset(_configurationManager.GetCVar<string>("game.defaultpreset"));
@@ -156,9 +160,13 @@ namespace Content.Server.GameTicking
RoundLengthMetric.Inc(frameEventArgs.DeltaSeconds); RoundLengthMetric.Inc(frameEventArgs.DeltaSeconds);
} }
if (RunLevel != GameRunLevel.PreRoundLobby || _roundStartTimeUtc > DateTime.UtcNow || if (RunLevel != GameRunLevel.PreRoundLobby ||
Paused ||
_roundStartTimeUtc > DateTime.UtcNow ||
_roundStartCountdownHasNotStartedYetDueToNoPlayers) _roundStartCountdownHasNotStartedYetDueToNoPlayers)
{
return; return;
}
StartRound(); StartRound();
} }
@@ -197,7 +205,7 @@ namespace Content.Server.GameTicking
} }
} }
public void StartRound() public void StartRound(bool force = false)
{ {
DebugTools.Assert(RunLevel == GameRunLevel.PreRoundLobby); DebugTools.Assert(RunLevel == GameRunLevel.PreRoundLobby);
Logger.InfoS("ticker", "Starting round!"); Logger.InfoS("ticker", "Starting round!");
@@ -247,13 +255,15 @@ namespace Content.Server.GameTicking
// Time to start the preset. // Time to start the preset.
var preset = MakeGamePreset(); var preset = MakeGamePreset();
if (!preset.Start(assignedJobs.Keys.ToList())) if (!preset.Start(assignedJobs.Keys.ToList(), force))
{ {
SetStartPreset(_configurationManager.GetCVar<string>("game.fallbackpreset")); SetStartPreset(_configurationManager.GetCVar<string>("game.fallbackpreset"));
var newPreset = MakeGamePreset(); var newPreset = MakeGamePreset();
_chatManager.DispatchServerAnnouncement($"Failed to start {preset.ModeTitle} mode! Defaulting to {newPreset.ModeTitle}..."); _chatManager.DispatchServerAnnouncement($"Failed to start {preset.ModeTitle} mode! Defaulting to {newPreset.ModeTitle}...");
if(!newPreset.Start(readyPlayers)) if (!newPreset.Start(readyPlayers, force))
{
throw new ApplicationException("Fallback preset failed to start!"); throw new ApplicationException("Fallback preset failed to start!");
}
} }
_roundStartTimeSpan = IoCManager.Resolve<IGameTiming>().RealTime; _roundStartTimeSpan = IoCManager.Resolve<IGameTiming>().RealTime;
@@ -297,7 +307,7 @@ namespace Content.Server.GameTicking
{ {
PlayerOOCName = ply.Name, PlayerOOCName = ply.Name,
PlayerICName = mind.CurrentEntity.Name, PlayerICName = mind.CurrentEntity.Name,
Role = antag ? mind.AllRoles.First(role => role.Antag).Name : mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("Unkown"), Role = antag ? mind.AllRoles.First(role => role.Antag).Name : mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("Unknown"),
Antag = antag Antag = antag
}; };
listOfPlayerInfo.Add(playerEndRoundInfo); listOfPlayerInfo.Add(playerEndRoundInfo);
@@ -377,22 +387,90 @@ namespace Content.Server.GameTicking
public IEnumerable<GameRule> ActiveGameRules => _gameRules; public IEnumerable<GameRule> ActiveGameRules => _gameRules;
public void SetStartPreset(Type type) public bool TryGetPreset(string name, out Type type)
{
type = name.ToLower() switch
{
"sandbox" => typeof(PresetSandbox),
"deathmatch" => typeof(PresetDeathMatch),
"suspicion" => typeof(PresetSuspicion),
_ => default
};
return type != default;
}
public void SetStartPreset(Type type, bool force = false)
{ {
if (!typeof(GamePreset).IsAssignableFrom(type)) throw new ArgumentException("type must inherit GamePreset"); if (!typeof(GamePreset).IsAssignableFrom(type)) throw new ArgumentException("type must inherit GamePreset");
_presetType = type; _presetType = type;
UpdateInfoText(); UpdateInfoText();
if (force)
{
StartRound(true);
}
} }
public void SetStartPreset(string type) => public void SetStartPreset(string name, bool force = false)
SetStartPreset(type switch {
if (!TryGetPreset(name, out var type))
{ {
"Sandbox" => typeof(PresetSandbox), throw new NotSupportedException();
"DeathMatch" => typeof(PresetDeathMatch), }
"Suspicion" => typeof(PresetSuspicion),
_ => throw new NotSupportedException() SetStartPreset(type, force);
}); }
public bool DelayStart(TimeSpan time)
{
if (_runLevel != GameRunLevel.PreRoundLobby)
{
return false;
}
_roundStartTimeUtc += time;
var lobbyCountdownMessage = _netManager.CreateNetMessage<MsgTickerLobbyCountdown>();
lobbyCountdownMessage.StartTime = _roundStartTimeUtc;
lobbyCountdownMessage.Paused = Paused;
_netManager.ServerSendToAll(lobbyCountdownMessage);
return true;
}
public bool PauseStart(bool pause = true)
{
if (Paused == pause)
{
return false;
}
Paused = pause;
if (pause)
{
_pauseTime = DateTime.UtcNow;
}
else if (_pauseTime != default)
{
_roundStartTimeUtc += DateTime.UtcNow - _pauseTime;
}
var lobbyCountdownMessage = _netManager.CreateNetMessage<MsgTickerLobbyCountdown>();
lobbyCountdownMessage.StartTime = _roundStartTimeUtc;
lobbyCountdownMessage.Paused = Paused;
_netManager.ServerSendToAll(lobbyCountdownMessage);
return true;
}
public bool TogglePause()
{
PauseStart(!Paused);
return Paused;
}
private IEntity _spawnPlayerMob(Job job, bool lateJoin = true) private IEntity _spawnPlayerMob(Job job, bool lateJoin = true)
{ {

View File

@@ -9,6 +9,47 @@ using Robust.Shared.Network;
namespace Content.Server.GameTicking namespace Content.Server.GameTicking
{ {
class DelayStartCommand : IClientCommand
{
public string Command => "delaystart";
public string Description => "Delays the round start.";
public string Help => $"Usage: {Command} <seconds>\nPauses/Resumes the countdown if no argument is provided.";
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
{
var ticker = IoCManager.Resolve<IGameTicker>();
if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
{
shell.SendText(player, "This can only be executed while the game is in the pre-round lobby.");
return;
}
if (args.Length == 0)
{
var paused = ticker.TogglePause();
shell.SendText(player, paused ? "Paused the countdown." : "Resumed the countdown.");
return;
}
if (args.Length != 1)
{
shell.SendText(player, "Need zero or one arguments.");
return;
}
if (!uint.TryParse(args[0], out var seconds) || seconds == 0)
{
shell.SendText(player, $"{args[0]} isn't a valid amount of seconds.");
return;
}
var time = TimeSpan.FromSeconds(seconds);
if (!ticker.DelayStart(time))
{
shell.SendText(player, "An unknown error has occurred.");
}
}
}
class StartRoundCommand : IClientCommand class StartRoundCommand : IClientCommand
{ {
@@ -193,4 +234,37 @@ namespace Content.Server.GameTicking
ticker.SetStartPreset(args[0]); ticker.SetStartPreset(args[0]);
} }
} }
class ForcePresetCommand : IClientCommand
{
public string Command => "forcepreset";
public string Description => "Forces a specific game preset to start for the current lobby.";
public string Help => $"Usage: {Command} <preset>";
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
{
var ticker = IoCManager.Resolve<IGameTicker>();
if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
{
shell.SendText(player, "This can only be executed while the game is in the pre-round lobby.");
return;
}
if (args.Length != 1)
{
shell.SendText(player, "Need exactly one argument.");
return;
}
var name = args[0];
if (!ticker.TryGetPreset(name, out var type))
{
shell.SendText(player, $"No preset exists with name {name}.");
return;
}
ticker.SetStartPreset(type, true);
shell.SendText(player, $"Forced the game to start with preset {name}.");
}
}
} }

View File

@@ -21,7 +21,7 @@ namespace Content.Server.Interfaces.GameTicking
void Update(FrameEventArgs frameEventArgs); void Update(FrameEventArgs frameEventArgs);
void RestartRound(); void RestartRound();
void StartRound(); void StartRound(bool force = false);
void EndRound(); void EndRound();
void Respawn(IPlayerSession targetPlayer); void Respawn(IPlayerSession targetPlayer);
@@ -39,7 +39,16 @@ namespace Content.Server.Interfaces.GameTicking
void RemoveGameRule(GameRule rule); void RemoveGameRule(GameRule rule);
IEnumerable<GameRule> ActiveGameRules { get; } IEnumerable<GameRule> ActiveGameRules { get; }
void SetStartPreset(Type type); bool TryGetPreset(string name, out Type type);
void SetStartPreset(string type); void SetStartPreset(Type type, bool force = false);
void SetStartPreset(string name, bool force = false);
/// <returns>true if changed, false otherwise</returns>
bool PauseStart(bool pause = true);
/// <returns>true if paused, false otherwise</returns>
bool TogglePause();
bool DelayStart(TimeSpan time);
} }
} }

View File

@@ -116,6 +116,40 @@ namespace Content.Shared
buffer.Write(TextBlob); buffer.Write(TextBlob);
} }
} }
protected class MsgTickerLobbyCountdown : NetMessage
{
#region REQUIRED
public const MsgGroups GROUP = MsgGroups.Command;
public const string NAME = nameof(MsgTickerLobbyCountdown);
public MsgTickerLobbyCountdown(INetChannel channel) : base(NAME, GROUP) { }
#endregion
/// <summary>
/// The total amount of seconds to go until the countdown finishes
/// </summary>
public DateTime StartTime { get; set; }
/// <summary>
/// Whether or not the countdown is paused
/// </summary>
public bool Paused { get; set; }
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
StartTime = new DateTime(buffer.ReadInt64(), DateTimeKind.Utc);
Paused = buffer.ReadBoolean();
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
buffer.Write(StartTime.Ticks);
buffer.Write(Paused);
}
}
public struct RoundEndPlayerInfo public struct RoundEndPlayerInfo
{ {
public string PlayerOOCName; public string PlayerOOCName;

View File

@@ -55,6 +55,8 @@
- tp - tp
- tpgrid - tpgrid
- setgamepreset - setgamepreset
- forcepreset
- delaystart
- startround - startround
- endround - endround
- restartround - restartround
@@ -98,6 +100,8 @@
- tp - tp
- tpgrid - tpgrid
- setgamepreset - setgamepreset
- forcepreset
- delaystart
- startround - startround
- endround - endround
- restartround - restartround