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

View File

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

View File

@@ -123,6 +123,13 @@ namespace Content.Client.State
}
string text;
if (_clientGameTicker.Paused)
{
text = Loc.GetString("Paused");
}
else
{
var difference = _clientGameTicker.StartTime - DateTime.UtcNow;
if (difference.Ticks < 0)
{
@@ -139,6 +146,7 @@ namespace Content.Client.State
{
text = $"{(int) Math.Floor(difference.TotalMinutes)}:{difference.Seconds:D2}";
}
}
_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 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>
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 Description => "Secret!";
}

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameTicking.GameRules;
using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameTicking;
@@ -28,16 +27,17 @@ namespace Content.Server.GameTicking.GamePresets
public int MinTraitors { get; set; } = 2;
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.");
return false;
}
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++)
{

View File

@@ -81,6 +81,7 @@ namespace Content.Server.GameTicking
[ViewVariables] private Type _presetType;
[ViewVariables] private DateTime _pauseTime;
[ViewVariables] private bool _roundStartCountdownHasNotStartedYetDueToNoPlayers;
private DateTime _roundStartTimeUtc;
[ViewVariables] private GameRunLevel _runLevel;
@@ -92,6 +93,8 @@ namespace Content.Server.GameTicking
private CancellationTokenSource _updateShutdownCts;
[ViewVariables] public bool Paused { get; private set; }
[ViewVariables]
public GameRunLevel RunLevel
{
@@ -128,6 +131,7 @@ namespace Content.Server.GameTicking
_netManager.RegisterNetMessage<MsgTickerJoinGame>(nameof(MsgTickerJoinGame));
_netManager.RegisterNetMessage<MsgTickerLobbyStatus>(nameof(MsgTickerLobbyStatus));
_netManager.RegisterNetMessage<MsgTickerLobbyInfo>(nameof(MsgTickerLobbyInfo));
_netManager.RegisterNetMessage<MsgTickerLobbyCountdown>(nameof(MsgTickerLobbyCountdown));
_netManager.RegisterNetMessage<MsgRoundEndMessage>(nameof(MsgRoundEndMessage));
SetStartPreset(_configurationManager.GetCVar<string>("game.defaultpreset"));
@@ -156,9 +160,13 @@ namespace Content.Server.GameTicking
RoundLengthMetric.Inc(frameEventArgs.DeltaSeconds);
}
if (RunLevel != GameRunLevel.PreRoundLobby || _roundStartTimeUtc > DateTime.UtcNow ||
if (RunLevel != GameRunLevel.PreRoundLobby ||
Paused ||
_roundStartTimeUtc > DateTime.UtcNow ||
_roundStartCountdownHasNotStartedYetDueToNoPlayers)
{
return;
}
StartRound();
}
@@ -197,7 +205,7 @@ namespace Content.Server.GameTicking
}
}
public void StartRound()
public void StartRound(bool force = false)
{
DebugTools.Assert(RunLevel == GameRunLevel.PreRoundLobby);
Logger.InfoS("ticker", "Starting round!");
@@ -247,14 +255,16 @@ namespace Content.Server.GameTicking
// Time to start the preset.
var preset = MakeGamePreset();
if (!preset.Start(assignedJobs.Keys.ToList()))
if (!preset.Start(assignedJobs.Keys.ToList(), force))
{
SetStartPreset(_configurationManager.GetCVar<string>("game.fallbackpreset"));
var newPreset = MakeGamePreset();
_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!");
}
}
_roundStartTimeSpan = IoCManager.Resolve<IGameTiming>().RealTime;
_sendStatusToAll();
@@ -297,7 +307,7 @@ namespace Content.Server.GameTicking
{
PlayerOOCName = ply.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
};
listOfPlayerInfo.Add(playerEndRoundInfo);
@@ -377,22 +387,90 @@ namespace Content.Server.GameTicking
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");
_presetType = type;
UpdateInfoText();
if (force)
{
StartRound(true);
}
}
public void SetStartPreset(string type) =>
SetStartPreset(type switch
public void SetStartPreset(string name, bool force = false)
{
"Sandbox" => typeof(PresetSandbox),
"DeathMatch" => typeof(PresetDeathMatch),
"Suspicion" => typeof(PresetSuspicion),
_ => throw new NotSupportedException()
});
if (!TryGetPreset(name, out var type))
{
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)
{

View File

@@ -9,6 +9,47 @@ using Robust.Shared.Network;
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
{
@@ -193,4 +234,37 @@ namespace Content.Server.GameTicking
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 RestartRound();
void StartRound();
void StartRound(bool force = false);
void EndRound();
void Respawn(IPlayerSession targetPlayer);
@@ -39,7 +39,16 @@ namespace Content.Server.Interfaces.GameTicking
void RemoveGameRule(GameRule rule);
IEnumerable<GameRule> ActiveGameRules { get; }
void SetStartPreset(Type type);
void SetStartPreset(string type);
bool TryGetPreset(string name, out Type 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);
}
}
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 string PlayerOOCName;

View File

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