Turns GameTicker into an EntitySystem. (#4197)

* GameTicker turned into an EntitySystem

* Turns ClientGameTicker into an EntitySystem, turn NetMessages into events

* Change event names to be more consistent with the rest.

* YAML linter uses the dummy gameticker CVar override.

* Fix game ticker initialization order

* Dummy ticker won't spawn players.

* Fix character creation test
This commit is contained in:
Vera Aguilera Puerto
2021-06-20 10:09:24 +02:00
committed by GitHub
parent 15fb554c28
commit d3a611164b
81 changed files with 1711 additions and 1990 deletions

View File

@@ -26,7 +26,6 @@ namespace Content.Client.Audio
[Dependency] private readonly IConfigurationManager _configManager = default!; [Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IBaseClient _client = default!; [Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IClientGameTicker _clientGameTicker = default!;
private SoundCollectionPrototype _ambientCollection = default!; private SoundCollectionPrototype _ambientCollection = default!;
@@ -50,7 +49,7 @@ namespace Content.Client.Audio
_client.PlayerJoinedServer += OnJoin; _client.PlayerJoinedServer += OnJoin;
_client.PlayerLeaveServer += OnLeave; _client.PlayerLeaveServer += OnLeave;
_clientGameTicker.LobbyStatusUpdated += LobbySongReceived; Get<ClientGameTicker>().LobbyStatusUpdated += LobbySongReceived;
} }
public override void Shutdown() public override void Shutdown()
@@ -62,7 +61,7 @@ namespace Content.Client.Audio
_client.PlayerJoinedServer -= OnJoin; _client.PlayerJoinedServer -= OnJoin;
_client.PlayerLeaveServer -= OnLeave; _client.PlayerLeaveServer -= OnLeave;
_clientGameTicker.LobbyStatusUpdated -= LobbySongReceived; Get<ClientGameTicker>().LobbyStatusUpdated -= LobbySongReceived;
EndAmbience(); EndAmbience();
EndLobbyMusic(); EndLobbyMusic();
@@ -167,7 +166,7 @@ namespace Content.Client.Audio
private void StartLobbyMusic() private void StartLobbyMusic()
{ {
EndLobbyMusic(); EndLobbyMusic();
var file = _clientGameTicker.LobbySong; var file = Get<ClientGameTicker>().LobbySong;
if (file == null) // We have not received the lobby song yet. if (file == null) // We have not received the lobby song yet.
{ {
return; return;

View File

@@ -167,7 +167,6 @@ namespace Content.Client.Entry
IoCManager.Resolve<IGameHud>().Initialize(); IoCManager.Resolve<IGameHud>().Initialize();
IoCManager.Resolve<IClientNotifyManager>().Initialize(); IoCManager.Resolve<IClientNotifyManager>().Initialize();
IoCManager.Resolve<IClientGameTicker>().Initialize();
var overlayMgr = IoCManager.Resolve<IOverlayManager>(); var overlayMgr = IoCManager.Resolve<IOverlayManager>();
overlayMgr.AddOverlay(new ParallaxOverlay()); overlayMgr.AddOverlay(new ParallaxOverlay());

View File

@@ -5,6 +5,7 @@ using Content.Client.RoundEnd;
using Content.Client.Viewport; using Content.Client.Viewport;
using Content.Shared.GameTicking; using Content.Shared.GameTicking;
using Content.Shared.GameWindow; using Content.Shared.GameWindow;
using JetBrains.Annotations;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.State; using Robust.Client.State;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -14,10 +15,11 @@ using Robust.Shared.ViewVariables;
namespace Content.Client.GameTicking.Managers namespace Content.Client.GameTicking.Managers
{ {
public class ClientGameTicker : SharedGameTicker, IClientGameTicker [UsedImplicitly]
public class ClientGameTicker : SharedGameTicker
{ {
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IClientNetManager _netManager = default!;
[ViewVariables] private bool _initialized; [ViewVariables] private bool _initialized;
private readonly List<string> _jobsAvailable = new(); private readonly List<string> _jobsAvailable = new();
@@ -29,7 +31,7 @@ namespace Content.Client.GameTicking.Managers
[ViewVariables] public string? ServerInfoBlob { get; private set; } [ViewVariables] public string? ServerInfoBlob { get; private set; }
[ViewVariables] public TimeSpan StartTime { get; private set; } [ViewVariables] public TimeSpan StartTime { get; private set; }
[ViewVariables] public bool Paused { get; private set; } [ViewVariables] public bool Paused { get; private set; }
[ViewVariables] public Dictionary<NetUserId, PlayerStatus> Status { get; private set; } = new(); [ViewVariables] public Dictionary<NetUserId, LobbyPlayerStatus> Status { get; private set; } = new();
[ViewVariables] public IReadOnlyList<string> JobsAvailable => _jobsAvailable; [ViewVariables] public IReadOnlyList<string> JobsAvailable => _jobsAvailable;
public event Action? InfoBlobUpdated; public event Action? InfoBlobUpdated;
@@ -38,47 +40,47 @@ namespace Content.Client.GameTicking.Managers
public event Action? LobbyLateJoinStatusUpdated; public event Action? LobbyLateJoinStatusUpdated;
public event Action<IReadOnlyList<string>>? LobbyJobsAvailableUpdated; public event Action<IReadOnlyList<string>>? LobbyJobsAvailableUpdated;
public void Initialize() public override void Initialize()
{ {
DebugTools.Assert(!_initialized); DebugTools.Assert(!_initialized);
_netManager.RegisterNetMessage<MsgTickerJoinLobby>(nameof(MsgTickerJoinLobby), JoinLobby); SubscribeNetworkEvent<TickerJoinLobbyEvent>(JoinLobby);
_netManager.RegisterNetMessage<MsgTickerJoinGame>(nameof(MsgTickerJoinGame), JoinGame); SubscribeNetworkEvent<TickerJoinGameEvent>(JoinGame);
_netManager.RegisterNetMessage<MsgTickerLobbyStatus>(nameof(MsgTickerLobbyStatus), LobbyStatus); SubscribeNetworkEvent<TickerLobbyStatusEvent>(LobbyStatus);
_netManager.RegisterNetMessage<MsgTickerLobbyInfo>(nameof(MsgTickerLobbyInfo), LobbyInfo); SubscribeNetworkEvent<TickerLobbyInfoEvent>(LobbyInfo);
_netManager.RegisterNetMessage<MsgTickerLobbyCountdown>(nameof(MsgTickerLobbyCountdown), LobbyCountdown); SubscribeNetworkEvent<TickerLobbyCountdownEvent>(LobbyCountdown);
_netManager.RegisterNetMessage<MsgTickerLobbyReady>(nameof(MsgTickerLobbyReady), LobbyReady); SubscribeNetworkEvent<TickerLobbyReadyEvent>(LobbyReady);
_netManager.RegisterNetMessage<MsgRoundEndMessage>(nameof(MsgRoundEndMessage), RoundEnd); SubscribeNetworkEvent<RoundEndMessageEvent>(RoundEnd);
_netManager.RegisterNetMessage<MsgRequestWindowAttention>(nameof(MsgRequestWindowAttention), msg => SubscribeNetworkEvent<RequestWindowAttentionEvent>(msg =>
{ {
IoCManager.Resolve<IClyde>().RequestWindowAttention(); IoCManager.Resolve<IClyde>().RequestWindowAttention();
}); });
_netManager.RegisterNetMessage<MsgTickerLateJoinStatus>(nameof(MsgTickerLateJoinStatus), LateJoinStatus); SubscribeNetworkEvent<TickerLateJoinStatusEvent>(LateJoinStatus);
_netManager.RegisterNetMessage<MsgTickerJobsAvailable>(nameof(MsgTickerJobsAvailable), UpdateJobsAvailable); SubscribeNetworkEvent<TickerJobsAvailableEvent>(UpdateJobsAvailable);
Status = new Dictionary<NetUserId, PlayerStatus>(); Status = new Dictionary<NetUserId, LobbyPlayerStatus>();
_initialized = true; _initialized = true;
} }
private void LateJoinStatus(MsgTickerLateJoinStatus message) private void LateJoinStatus(TickerLateJoinStatusEvent message)
{ {
DisallowedLateJoin = message.Disallowed; DisallowedLateJoin = message.Disallowed;
LobbyLateJoinStatusUpdated?.Invoke(); LobbyLateJoinStatusUpdated?.Invoke();
} }
private void UpdateJobsAvailable(MsgTickerJobsAvailable message) private void UpdateJobsAvailable(TickerJobsAvailableEvent message)
{ {
_jobsAvailable.Clear(); _jobsAvailable.Clear();
_jobsAvailable.AddRange(message.JobsAvailable); _jobsAvailable.AddRange(message.JobsAvailable);
LobbyJobsAvailableUpdated?.Invoke(JobsAvailable); LobbyJobsAvailableUpdated?.Invoke(JobsAvailable);
} }
private void JoinLobby(MsgTickerJoinLobby message) private void JoinLobby(TickerJoinLobbyEvent message)
{ {
_stateManager.RequestStateChange<LobbyState>(); _stateManager.RequestStateChange<LobbyState>();
} }
private void LobbyStatus(MsgTickerLobbyStatus message) private void LobbyStatus(TickerLobbyStatusEvent message)
{ {
StartTime = message.StartTime; StartTime = message.StartTime;
IsGameStarted = message.IsRoundStarted; IsGameStarted = message.IsRoundStarted;
@@ -91,35 +93,35 @@ namespace Content.Client.GameTicking.Managers
LobbyStatusUpdated?.Invoke(); LobbyStatusUpdated?.Invoke();
} }
private void LobbyInfo(MsgTickerLobbyInfo message) private void LobbyInfo(TickerLobbyInfoEvent message)
{ {
ServerInfoBlob = message.TextBlob; ServerInfoBlob = message.TextBlob;
InfoBlobUpdated?.Invoke(); InfoBlobUpdated?.Invoke();
} }
private void JoinGame(MsgTickerJoinGame message) private void JoinGame(TickerJoinGameEvent message)
{ {
_stateManager.RequestStateChange<GameScreen>(); _stateManager.RequestStateChange<GameScreen>();
} }
private void LobbyCountdown(MsgTickerLobbyCountdown message) private void LobbyCountdown(TickerLobbyCountdownEvent message)
{ {
StartTime = message.StartTime; StartTime = message.StartTime;
Paused = message.Paused; Paused = message.Paused;
} }
private void LobbyReady(MsgTickerLobbyReady message) private void LobbyReady(TickerLobbyReadyEvent message)
{ {
// Merge the Dictionaries // Merge the Dictionaries
foreach (var p in message.PlayerStatus) foreach (var p in message.Status)
{ {
Status[p.Key] = p.Value; Status[p.Key] = p.Value;
} }
LobbyReadyUpdated?.Invoke(); LobbyReadyUpdated?.Invoke();
} }
private void RoundEnd(MsgRoundEndMessage message) private void RoundEnd(RoundEndMessageEvent message)
{ {
//This is not ideal at all, but I don't see an immediately better fit anywhere else. //This is not ideal at all, but I don't see an immediately better fit anywhere else.
var roundEnd = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, message.RoundDuration, message.AllPlayersEndInfo); var roundEnd = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, message.RoundDuration, message.AllPlayersEndInfo);

View File

@@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Network;
using static Content.Shared.GameTicking.SharedGameTicker;
namespace Content.Client.GameTicking.Managers
{
public interface IClientGameTicker
{
bool IsGameStarted { get; }
string? ServerInfoBlob { get; }
bool AreWeReady { get; }
string? LobbySong { get; }
bool DisallowedLateJoin { get; }
TimeSpan StartTime { get; }
bool Paused { get; }
Dictionary<NetUserId, PlayerStatus> Status { get; }
IReadOnlyList<string> JobsAvailable { get; }
void Initialize();
event Action InfoBlobUpdated;
event Action LobbyStatusUpdated;
event Action LobbyReadyUpdated;
event Action LobbyLateJoinStatusUpdated;
event Action<IReadOnlyList<string>> LobbyJobsAvailableUpdated;
}
}

View File

@@ -33,7 +33,6 @@ namespace Content.Client.IoC
IoCManager.Register<IGameHud, GameHud>(); IoCManager.Register<IGameHud, GameHud>();
IoCManager.Register<IClientNotifyManager, ClientNotifyManager>(); IoCManager.Register<IClientNotifyManager, ClientNotifyManager>();
IoCManager.Register<ISharedNotifyManager, ClientNotifyManager>(); IoCManager.Register<ISharedNotifyManager, ClientNotifyManager>();
IoCManager.Register<IClientGameTicker, ClientGameTicker>();
IoCManager.Register<IParallaxManager, ParallaxManager>(); IoCManager.Register<IParallaxManager, ParallaxManager>();
IoCManager.Register<IChatManager, ChatManager>(); IoCManager.Register<IChatManager, ChatManager>();
IoCManager.Register<IEscapeMenuOwner, EscapeMenuOwner>(); IoCManager.Register<IEscapeMenuOwner, EscapeMenuOwner>();

View File

@@ -9,6 +9,7 @@ using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.Utility; using Robust.Client.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Log; using Robust.Shared.Log;
@@ -22,7 +23,6 @@ namespace Content.Client.LateJoin
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!; [Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IClientGameTicker _gameTicker = default!;
public event Action<string>? SelectedId; public event Action<string>? SelectedId;
@@ -34,6 +34,8 @@ namespace Content.Client.LateJoin
MinSize = SetSize = (360, 560); MinSize = SetSize = (360, 560);
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
var gameTicker = EntitySystem.Get<ClientGameTicker>();
Title = Loc.GetString("Late Join"); Title = Loc.GetString("Late Join");
var jobList = new VBoxContainer(); var jobList = new VBoxContainer();
@@ -131,7 +133,7 @@ namespace Content.Client.LateJoin
SelectedId?.Invoke(jobButton.JobId); SelectedId?.Invoke(jobButton.JobId);
}; };
if (!_gameTicker.JobsAvailable.Contains(job.ID)) if (!gameTicker.JobsAvailable.Contains(job.ID))
{ {
jobButton.Disabled = true; jobButton.Disabled = true;
} }
@@ -147,7 +149,7 @@ namespace Content.Client.LateJoin
Close(); Close();
}; };
_gameTicker.LobbyJobsAvailableUpdated += JobsAvailableUpdated; gameTicker.LobbyJobsAvailableUpdated += JobsAvailableUpdated;
} }
private void JobsAvailableUpdated(IReadOnlyList<string> jobs) private void JobsAvailableUpdated(IReadOnlyList<string> jobs)
@@ -164,7 +166,7 @@ namespace Content.Client.LateJoin
if (disposing) if (disposing)
{ {
_gameTicker.LobbyJobsAvailableUpdated -= JobsAvailableUpdated; EntitySystem.Get<ClientGameTicker>().LobbyJobsAvailableUpdated -= JobsAvailableUpdated;
_jobButtons.Clear(); _jobButtons.Clear();
_jobCategories.Clear(); _jobCategories.Clear();
} }

View File

@@ -10,6 +10,8 @@ using Content.Client.Preferences.UI;
using Content.Client.Viewport; using Content.Client.Viewport;
using Content.Client.Voting; using Content.Client.Voting;
using Content.Shared.Chat; using Content.Shared.Chat;
using Content.Shared.GameObjects.Components;
using Content.Shared.GameTicking;
using Content.Shared.Input; using Content.Shared.Input;
using Robust.Client; using Robust.Client;
using Robust.Client.Console; using Robust.Client.Console;
@@ -38,7 +40,6 @@ namespace Content.Client.Lobby
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IClientGameTicker _clientGameTicker = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!; [Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!; [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
@@ -50,6 +51,7 @@ namespace Content.Client.Lobby
public override void Startup() public override void Startup()
{ {
var gameTicker = EntitySystem.Get<ClientGameTicker>();
_characterSetup = new CharacterSetupGui(_entityManager, _resourceCache, _preferencesManager, _characterSetup = new CharacterSetupGui(_entityManager, _resourceCache, _preferencesManager,
_prototypeManager); _prototypeManager);
LayoutContainer.SetAnchorPreset(_characterSetup, LayoutContainer.LayoutPreset.Wide); LayoutContainer.SetAnchorPreset(_characterSetup, LayoutContainer.LayoutPreset.Wide);
@@ -105,7 +107,7 @@ namespace Content.Client.Lobby
_lobby.ObserveButton.OnPressed += _ => _consoleHost.ExecuteCommand("observe"); _lobby.ObserveButton.OnPressed += _ => _consoleHost.ExecuteCommand("observe");
_lobby.ReadyButton.OnPressed += _ => _lobby.ReadyButton.OnPressed += _ =>
{ {
if (!_clientGameTicker.IsGameStarted) if (!gameTicker.IsGameStarted)
{ {
return; return;
} }
@@ -124,29 +126,23 @@ namespace Content.Client.Lobby
UpdatePlayerList(); UpdatePlayerList();
_playerManager.PlayerListUpdated += PlayerManagerOnPlayerListUpdated; _playerManager.PlayerListUpdated += PlayerManagerOnPlayerListUpdated;
_clientGameTicker.InfoBlobUpdated += UpdateLobbyUi; gameTicker.InfoBlobUpdated += UpdateLobbyUi;
_clientGameTicker.LobbyStatusUpdated += LobbyStatusUpdated; gameTicker.LobbyStatusUpdated += LobbyStatusUpdated;
_clientGameTicker.LobbyReadyUpdated += LobbyReadyUpdated; gameTicker.LobbyReadyUpdated += LobbyReadyUpdated;
_clientGameTicker.LobbyLateJoinStatusUpdated += LobbyLateJoinStatusUpdated; gameTicker.LobbyLateJoinStatusUpdated += LobbyLateJoinStatusUpdated;
} }
public override void Shutdown() public override void Shutdown()
{ {
_playerManager.PlayerListUpdated -= PlayerManagerOnPlayerListUpdated; _playerManager.PlayerListUpdated -= PlayerManagerOnPlayerListUpdated;
_clientGameTicker.InfoBlobUpdated -= UpdateLobbyUi;
_clientGameTicker.LobbyStatusUpdated -= LobbyStatusUpdated;
_clientGameTicker.LobbyReadyUpdated -= LobbyReadyUpdated;
_clientGameTicker.LobbyLateJoinStatusUpdated -= LobbyLateJoinStatusUpdated;
_clientGameTicker.Status.Clear();
_lobby.Dispose(); _lobby.Dispose();
_characterSetup.Dispose(); _characterSetup.Dispose();
} }
public override void FrameUpdate(FrameEventArgs e) public override void FrameUpdate(FrameEventArgs e)
{ {
if (_clientGameTicker.IsGameStarted) var gameTicker = EntitySystem.Get<ClientGameTicker>();
if (gameTicker.IsGameStarted)
{ {
_lobby.StartTime.Text = ""; _lobby.StartTime.Text = "";
return; return;
@@ -154,13 +150,13 @@ namespace Content.Client.Lobby
string text; string text;
if (_clientGameTicker.Paused) if (gameTicker.Paused)
{ {
text = Loc.GetString("Paused"); text = Loc.GetString("Paused");
} }
else else
{ {
var difference = _clientGameTicker.StartTime - _gameTiming.CurTime; var difference = gameTicker.StartTime - _gameTiming.CurTime;
var seconds = difference.TotalSeconds; var seconds = difference.TotalSeconds;
if (seconds < 0) if (seconds < 0)
{ {
@@ -177,15 +173,16 @@ namespace Content.Client.Lobby
private void PlayerManagerOnPlayerListUpdated(object? sender, EventArgs e) private void PlayerManagerOnPlayerListUpdated(object? sender, EventArgs e)
{ {
var gameTicker = EntitySystem.Get<ClientGameTicker>();
// Remove disconnected sessions from the Ready Dict // Remove disconnected sessions from the Ready Dict
foreach (var p in _clientGameTicker.Status) foreach (var p in gameTicker.Status)
{ {
if (!_playerManager.SessionsDict.TryGetValue(p.Key, out _)) if (!_playerManager.SessionsDict.TryGetValue(p.Key, out _))
{ {
// This is a shitty fix. Observers can rejoin because they are already in the game. // This is a shitty fix. Observers can rejoin because they are already in the game.
// So we don't delete them, but keep them if they decide to rejoin // So we don't delete them, but keep them if they decide to rejoin
if (p.Value != PlayerStatus.Observer) if (p.Value != LobbyPlayerStatus.Observer)
_clientGameTicker.Status.Remove(p.Key); gameTicker.Status.Remove(p.Key);
} }
} }
@@ -202,7 +199,7 @@ namespace Content.Client.Lobby
private void LobbyLateJoinStatusUpdated() private void LobbyLateJoinStatusUpdated()
{ {
_lobby.ReadyButton.Disabled = _clientGameTicker.DisallowedLateJoin; _lobby.ReadyButton.Disabled = EntitySystem.Get<ClientGameTicker>().DisallowedLateJoin;
} }
private void UpdateLobbyUi() private void UpdateLobbyUi()
@@ -212,7 +209,9 @@ namespace Content.Client.Lobby
return; return;
} }
if (_clientGameTicker.IsGameStarted) var gameTicker = EntitySystem.Get<ClientGameTicker>();
if (gameTicker.IsGameStarted)
{ {
_lobby.ReadyButton.Text = Loc.GetString("Join"); _lobby.ReadyButton.Text = Loc.GetString("Join");
_lobby.ReadyButton.ToggleMode = false; _lobby.ReadyButton.ToggleMode = false;
@@ -224,36 +223,37 @@ namespace Content.Client.Lobby
_lobby.ReadyButton.Text = Loc.GetString("Ready Up"); _lobby.ReadyButton.Text = Loc.GetString("Ready Up");
_lobby.ReadyButton.ToggleMode = true; _lobby.ReadyButton.ToggleMode = true;
_lobby.ReadyButton.Disabled = false; _lobby.ReadyButton.Disabled = false;
_lobby.ReadyButton.Pressed = _clientGameTicker.AreWeReady; _lobby.ReadyButton.Pressed = gameTicker.AreWeReady;
} }
if (_clientGameTicker.ServerInfoBlob != null) if (gameTicker.ServerInfoBlob != null)
{ {
_lobby.ServerInfo.SetInfoBlob(_clientGameTicker.ServerInfoBlob); _lobby.ServerInfo.SetInfoBlob(gameTicker.ServerInfoBlob);
} }
} }
private void UpdatePlayerList() private void UpdatePlayerList()
{ {
_lobby.OnlinePlayerList.Clear(); _lobby.OnlinePlayerList.Clear();
var gameTicker = EntitySystem.Get<ClientGameTicker>();
foreach (var session in _playerManager.Sessions.OrderBy(s => s.Name)) foreach (var session in _playerManager.Sessions.OrderBy(s => s.Name))
{ {
var readyState = ""; var readyState = "";
// Don't show ready state if we're ingame // Don't show ready state if we're ingame
if (!_clientGameTicker.IsGameStarted) if (!gameTicker.IsGameStarted)
{ {
PlayerStatus status; LobbyPlayerStatus status;
if (session.UserId == _playerManager.LocalPlayer?.UserId) if (session.UserId == _playerManager.LocalPlayer?.UserId)
status = _clientGameTicker.AreWeReady ? PlayerStatus.Ready : PlayerStatus.NotReady; status = gameTicker.AreWeReady ? LobbyPlayerStatus.Ready : LobbyPlayerStatus.NotReady;
else else
_clientGameTicker.Status.TryGetValue(session.UserId, out status); gameTicker.Status.TryGetValue(session.UserId, out status);
readyState = status switch readyState = status switch
{ {
PlayerStatus.NotReady => Loc.GetString("Not Ready"), LobbyPlayerStatus.NotReady => Loc.GetString("Not Ready"),
PlayerStatus.Ready => Loc.GetString("Ready"), LobbyPlayerStatus.Ready => Loc.GetString("Ready"),
PlayerStatus.Observer => Loc.GetString("Observer"), LobbyPlayerStatus.Observer => Loc.GetString("Observer"),
_ => "", _ => "",
}; };
} }
@@ -264,7 +264,7 @@ namespace Content.Client.Lobby
private void SetReady(bool newReady) private void SetReady(bool newReady)
{ {
if (_clientGameTicker.IsGameStarted) if (EntitySystem.Get<ClientGameTicker>().IsGameStarted)
{ {
return; return;
} }

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Client.Message; using Content.Client.Message;
using Content.Shared.GameTicking;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization; using Robust.Shared.Localization;
@@ -15,7 +16,7 @@ namespace Content.Client.RoundEnd
private VBoxContainer PlayerManifestoTab { get; } private VBoxContainer PlayerManifestoTab { get; }
private TabContainer RoundEndWindowTabs { get; } private TabContainer RoundEndWindowTabs { get; }
public RoundEndSummaryWindow(string gm, string roundEnd, TimeSpan roundTimeSpan, List<RoundEndPlayerInfo> info) public RoundEndSummaryWindow(string gm, string roundEnd, TimeSpan roundTimeSpan, RoundEndMessageEvent.RoundEndPlayerInfo[] info)
{ {
MinSize = SetSize = (520, 580); MinSize = SetSize = (520, 580);

View File

@@ -5,10 +5,12 @@ using Content.Client.Parallax.Managers;
using Content.Server.GameTicking; using Content.Server.GameTicking;
using Content.Server.IoC; using Content.Server.IoC;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Moq;
using NUnit.Framework; using NUnit.Framework;
using Robust.Server.Maps; using Robust.Server.Maps;
using Robust.Shared; using Robust.Shared;
using Robust.Shared.ContentPack; using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Log; using Robust.Shared.Log;
using Robust.Shared.Map; using Robust.Shared.Map;
@@ -57,7 +59,7 @@ namespace Content.IntegrationTests
// Connecting to Discord is a massive waste of time. // Connecting to Discord is a massive waste of time.
// Basically just makes the CI logs a mess. // Basically just makes the CI logs a mess.
options.CVarOverrides["discord.enabled"] = "false"; options.CVarOverrides[CVars.DiscordEnabled.Name] = "false";
// Avoid preloading textures in tests. // Avoid preloading textures in tests.
options.CVarOverrides.TryAdd(CVars.TexturePreloadingEnabled.Name, "false"); options.CVarOverrides.TryAdd(CVars.TexturePreloadingEnabled.Name, "false");
@@ -112,16 +114,9 @@ namespace Content.IntegrationTests
protected ServerIntegrationInstance StartServerDummyTicker(ServerIntegrationOptions options = null) protected ServerIntegrationInstance StartServerDummyTicker(ServerIntegrationOptions options = null)
{ {
options ??= new ServerIntegrationOptions(); options ??= new ServerIntegrationOptions();
options.BeforeStart += () =>
{ // Dummy game ticker.
IoCManager.Resolve<IModLoader>().SetModuleBaseCallbacks(new ServerModuleTestingCallbacks options.CVarOverrides[CCVars.GameDummyTicker.Name] = "true";
{
ServerBeforeIoC = () =>
{
IoCManager.Register<IGameTicker, DummyGameTicker>(true);
}
});
};
return StartServer(options); return StartServer(options);
} }

View File

@@ -1,140 +0,0 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules;
using Content.Server.Mind;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Content.IntegrationTests
{
public class DummyGameTicker : GameTickerBase, IGameTicker
{
public GameRunLevel RunLevel { get; } = GameRunLevel.InRound;
public MapId DefaultMap { get; } = MapId.Nullspace;
public GridId DefaultGridId { get; } = GridId.Invalid;
public event Action<GameRunLevelChangedEventArgs> OnRunLevelChanged
{
add { }
remove { }
}
public event Action<GameRuleAddedEventArgs> OnRuleAdded
{
add{ }
remove { }
}
public void Update(FrameEventArgs frameEventArgs)
{
}
public void RestartRound()
{
}
public void StartRound(bool force = false)
{
}
public void EndRound(string roundEnd)
{
}
public void Respawn(IPlayerSession targetPlayer)
{
}
public bool OnGhostAttempt(Mind mind, bool canReturnGlobal)
{
return false;
}
public void MakeObserve(IPlayerSession player)
{
}
public void MakeJoinGame(IPlayerSession player, string? jobId)
{
}
public void ToggleReady(IPlayerSession player, bool ready)
{
}
public void ToggleDisallowLateJoin(bool disallowLateJoin)
{
}
public EntityCoordinates GetLateJoinSpawnPoint() => EntityCoordinates.Invalid;
public EntityCoordinates GetJobSpawnPoint(string jobId) => EntityCoordinates.Invalid;
public EntityCoordinates GetObserverSpawnPoint() => EntityCoordinates.Invalid;
public void EquipStartingGear(IEntity entity, StartingGearPrototype startingGear, HumanoidCharacterProfile? profile)
{
}
public T AddGameRule<T>() where T : GameRule, new()
{
return new();
}
public bool HasGameRule(string? type)
{
return false;
}
public bool HasGameRule(Type? type)
{
return false;
}
public void RemoveGameRule(GameRule rule)
{
}
public IEnumerable<GameRule> ActiveGameRules { get; } = Array.Empty<GameRule>();
public bool TryGetPreset(string name, [NotNullWhen(true)] out Type? type)
{
type = default;
return false;
}
public void SetStartPreset(Type type, bool force = false)
{
}
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;
}
public Dictionary<string, int> GetAvailablePositions()
{
return new();
}
}
}

View File

@@ -24,9 +24,9 @@ namespace Content.IntegrationTests.Tests.Commands
await server.WaitIdleAsync(); await server.WaitIdleAsync();
var gameTicker = server.ResolveDependency<IGameTicker>();
var configManager = server.ResolveDependency<IConfigurationManager>(); var configManager = server.ResolveDependency<IConfigurationManager>();
var entityManager = server.ResolveDependency<IEntityManager>(); var entityManager = server.ResolveDependency<IEntityManager>();
var gameTicker = entityManager.EntitySysManager.GetEntitySystem<GameTicker>();
await server.WaitRunTicks(30); await server.WaitRunTicks(30);

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Content.Server.GameTicking; using Content.Server.GameTicking;
using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules;
using NUnit.Framework; using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.IntegrationTests.Tests.GameRules namespace Content.IntegrationTests.Tests.GameRules
@@ -25,7 +26,7 @@ namespace Content.IntegrationTests.Tests.GameRules
await server.WaitIdleAsync(); await server.WaitIdleAsync();
var sGameTicker = server.ResolveDependency<IGameTicker>(); var sGameTicker = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
var sGameTiming = server.ResolveDependency<IGameTiming>(); var sGameTiming = server.ResolveDependency<IGameTiming>();
RuleMaxTimeRestart maxTimeRule = null; RuleMaxTimeRestart maxTimeRule = null;

View File

@@ -13,6 +13,7 @@ using Content.Shared.Preferences;
using NUnit.Framework; using NUnit.Framework;
using Robust.Client.State; using Robust.Client.State;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Network; using Robust.Shared.Network;
namespace Content.IntegrationTests.Tests.Lobby namespace Content.IntegrationTests.Tests.Lobby
@@ -32,7 +33,7 @@ namespace Content.IntegrationTests.Tests.Lobby
var clientPrefManager = client.ResolveDependency<IClientPreferencesManager>(); var clientPrefManager = client.ResolveDependency<IClientPreferencesManager>();
var serverConfig = server.ResolveDependency<IConfigurationManager>(); var serverConfig = server.ResolveDependency<IConfigurationManager>();
var serverTicker = server.ResolveDependency<IGameTicker>(); var serverTicker = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<GameTicker>();
var serverPrefManager = server.ResolveDependency<IServerPreferencesManager>(); var serverPrefManager = server.ResolveDependency<IServerPreferencesManager>();
await server.WaitIdleAsync(); await server.WaitIdleAsync();
@@ -40,12 +41,16 @@ namespace Content.IntegrationTests.Tests.Lobby
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {
serverConfig.SetCVar(CCVars.GameDummyTicker, false);
serverConfig.SetCVar(CCVars.GameLobbyEnabled, true); serverConfig.SetCVar(CCVars.GameLobbyEnabled, true);
serverTicker.RestartRound(); serverTicker.RestartRound();
}); });
Assert.That(serverTicker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); Assert.That(serverTicker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby));
// Need to run them in sync to receive the messages.
await RunTicksSync(client, server, 1);
await WaitUntil(client, () => clientStateManager.CurrentState is LobbyState, maxTicks: 60); await WaitUntil(client, () => clientStateManager.CurrentState is LobbyState, maxTicks: 60);
Assert.NotNull(clientNetManager.ServerChannel); Assert.NotNull(clientNetManager.ServerChannel);

View File

@@ -36,8 +36,8 @@ namespace Content.IntegrationTests.Tests
await server.WaitIdleAsync(); await server.WaitIdleAsync();
var gameTicker = server.ResolveDependency<IGameTicker>();
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>(); var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
var gameTicker = entitySystemManager.GetEntitySystem<GameTicker>();
await server.WaitAssertion(() => await server.WaitAssertion(() =>
{ {

View File

@@ -1,6 +1,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.GameTicking; using Content.Server.GameTicking;
using NUnit.Framework; using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.IntegrationTests.Tests namespace Content.IntegrationTests.Tests
@@ -15,7 +16,7 @@ namespace Content.IntegrationTests.Tests
server.Post(() => server.Post(() =>
{ {
IoCManager.Resolve<IGameTicker>().RestartRound(); IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().RestartRound();
}); });
await RunTicksSync(client, server, 10); await RunTicksSync(client, server, 10);

View File

@@ -46,7 +46,7 @@ namespace Content.Server.AI.Components
if (StartingGearPrototype != null) if (StartingGearPrototype != null)
{ {
var gameTicker = IoCManager.Resolve<IGameTicker>(); var gameTicker = EntitySystem.Get<GameTicker>();
var protoManager = IoCManager.Resolve<IPrototypeManager>(); var protoManager = IoCManager.Resolve<IPrototypeManager>();
var startingGear = protoManager.Index<StartingGearPrototype>(StartingGearPrototype); var startingGear = protoManager.Index<StartingGearPrototype>(StartingGearPrototype);

View File

@@ -42,7 +42,7 @@ namespace Content.Server.Administration.Commands
var canReturn = mind.CurrentEntity != null; var canReturn = mind.CurrentEntity != null;
var ghost = IoCManager.Resolve<IEntityManager>() var ghost = IoCManager.Resolve<IEntityManager>()
.SpawnEntity("AdminObserver", player.AttachedEntity?.Transform.Coordinates .SpawnEntity("AdminObserver", player.AttachedEntity?.Transform.Coordinates
?? IoCManager.Resolve<IGameTicker>().GetObserverSpawnPoint()); ?? EntitySystem.Get<GameTicker>().GetObserverSpawnPoint());
if (canReturn) if (canReturn)
{ {

View File

@@ -3,6 +3,7 @@ using Content.Server.GameTicking;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Server.Administration.Commands namespace Content.Server.Administration.Commands
@@ -22,7 +23,7 @@ namespace Content.Server.Administration.Commands
ready = bool.Parse(args[0]); ready = bool.Parse(args[0]);
} }
var gameTicker = IoCManager.Resolve<IGameTicker>(); var gameTicker = EntitySystem.Get<GameTicker>();
var playerManager = IoCManager.Resolve<IPlayerManager>(); var playerManager = IoCManager.Resolve<IPlayerManager>();

View File

@@ -25,7 +25,6 @@ namespace Content.Server.Body
public class BodyComponent : SharedBodyComponent, IRelayMoveInput, IGhostOnMove public class BodyComponent : SharedBodyComponent, IRelayMoveInput, IGhostOnMove
{ {
private Container _partContainer = default!; private Container _partContainer = default!;
[Dependency] private readonly IGameTicker _gameTicker = default!;
protected override bool CanAddPart(string slotId, SharedBodyPartComponent part) protected override bool CanAddPart(string slotId, SharedBodyPartComponent part)
{ {
@@ -94,7 +93,7 @@ namespace Content.Server.Body
Owner.TryGetComponent(out MindComponent? mind) && Owner.TryGetComponent(out MindComponent? mind) &&
mind.HasMind) mind.HasMind)
{ {
_gameTicker.OnGhostAttempt(mind.Mind!, true); EntitySystem.Get<GameTicker>().OnGhostAttempt(mind.Mind!, true);
} }
} }

View File

@@ -125,7 +125,7 @@ namespace Content.Server.Chat.Commands
// Prevent the player from returning to the body. // Prevent the player from returning to the body.
// Note that mind cannot be null because otherwise owner would be null. // Note that mind cannot be null because otherwise owner would be null.
IoCManager.Resolve<IGameTicker>().OnGhostAttempt(mind!, false); EntitySystem.Get<GameTicker>().OnGhostAttempt(mind!, false);
} }
} }
} }

View File

@@ -14,7 +14,6 @@ using Content.Server.Notification.Managers;
using Content.Server.PDA.Managers; using Content.Server.PDA.Managers;
using Content.Server.Preferences.Managers; using Content.Server.Preferences.Managers;
using Content.Server.Sandbox; using Content.Server.Sandbox;
using Content.Server.Shell;
using Content.Server.Speech; using Content.Server.Speech;
using Content.Server.Voting.Managers; using Content.Server.Voting.Managers;
using Content.Shared.Actions; using Content.Shared.Actions;
@@ -31,9 +30,7 @@ namespace Content.Server.Entry
{ {
public class EntryPoint : GameServer public class EntryPoint : GameServer
{ {
private IGameTicker _gameTicker = default!;
private EuiManager _euiManager = default!; private EuiManager _euiManager = default!;
private StatusShell _statusShell = default!;
private IVoteManager _voteManager = default!; private IVoteManager _voteManager = default!;
/// <inheritdoc /> /// <inheritdoc />
@@ -60,7 +57,6 @@ namespace Content.Server.Entry
IoCManager.BuildGraph(); IoCManager.BuildGraph();
_gameTicker = IoCManager.Resolve<IGameTicker>();
_euiManager = IoCManager.Resolve<EuiManager>(); _euiManager = IoCManager.Resolve<EuiManager>();
_voteManager = IoCManager.Resolve<IVoteManager>(); _voteManager = IoCManager.Resolve<IVoteManager>();
@@ -69,8 +65,6 @@ namespace Content.Server.Entry
var playerManager = IoCManager.Resolve<IPlayerManager>(); var playerManager = IoCManager.Resolve<IPlayerManager>();
_statusShell = new StatusShell();
var logManager = IoCManager.Resolve<ILogManager>(); var logManager = IoCManager.Resolve<ILogManager>();
logManager.GetSawmill("Storage").Level = LogLevel.Info; logManager.GetSawmill("Storage").Level = LogLevel.Info;
logManager.GetSawmill("db.ef").Level = LogLevel.Info; logManager.GetSawmill("db.ef").Level = LogLevel.Info;
@@ -79,7 +73,6 @@ namespace Content.Server.Entry
IoCManager.Resolve<IServerDbManager>().Init(); IoCManager.Resolve<IServerDbManager>().Init();
IoCManager.Resolve<IServerPreferencesManager>().Init(); IoCManager.Resolve<IServerPreferencesManager>().Init();
IoCManager.Resolve<INodeGroupFactory>().Initialize(); IoCManager.Resolve<INodeGroupFactory>().Initialize();
IoCManager.Resolve<ISandboxManager>().Initialize();
IoCManager.Resolve<IAccentManager>().Initialize(); IoCManager.Resolve<IAccentManager>().Initialize();
_voteManager.Initialize(); _voteManager.Initialize();
} }
@@ -89,7 +82,7 @@ namespace Content.Server.Entry
base.PostInit(); base.PostInit();
IoCManager.Resolve<IHolidayManager>().Initialize(); IoCManager.Resolve<IHolidayManager>().Initialize();
_gameTicker.Initialize(); IoCManager.Resolve<ISandboxManager>().Initialize();
IoCManager.Resolve<RecipeManager>().Initialize(); IoCManager.Resolve<RecipeManager>().Initialize();
IoCManager.Resolve<AlertManager>().Initialize(); IoCManager.Resolve<AlertManager>().Initialize();
IoCManager.Resolve<ActionManager>().Initialize(); IoCManager.Resolve<ActionManager>().Initialize();
@@ -99,6 +92,8 @@ namespace Content.Server.Entry
IoCManager.Resolve<IAdminManager>().Initialize(); IoCManager.Resolve<IAdminManager>().Initialize();
IoCManager.Resolve<INpcBehaviorManager>().Initialize(); IoCManager.Resolve<INpcBehaviorManager>().Initialize();
_euiManager.Initialize(); _euiManager.Initialize();
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
} }
public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs) public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs)
@@ -107,11 +102,6 @@ namespace Content.Server.Entry
switch (level) switch (level)
{ {
case ModUpdateLevel.PreEngine:
{
_gameTicker.Update(frameEventArgs);
break;
}
case ModUpdateLevel.PostEngine: case ModUpdateLevel.PostEngine:
{ {
_euiManager.SendUpdates(); _euiManager.SendUpdates();

View File

@@ -2,6 +2,7 @@
using Content.Server.Administration; using Content.Server.Administration;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Server.GameTicking.Commands namespace Content.Server.GameTicking.Commands
@@ -15,7 +16,7 @@ namespace Content.Server.GameTicking.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args) public void Execute(IConsoleShell shell, string argStr, string[] args)
{ {
var ticker = IoCManager.Resolve<IGameTicker>(); var ticker = EntitySystem.Get<GameTicker>();
if (ticker.RunLevel != GameRunLevel.PreRoundLobby) if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
{ {
shell.WriteLine("This can only be executed while the game is in the pre-round lobby."); shell.WriteLine("This can only be executed while the game is in the pre-round lobby.");

View File

@@ -2,6 +2,7 @@
using Content.Server.Administration; using Content.Server.Administration;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Server.GameTicking.Commands namespace Content.Server.GameTicking.Commands
@@ -15,7 +16,7 @@ namespace Content.Server.GameTicking.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args) public void Execute(IConsoleShell shell, string argStr, string[] args)
{ {
var ticker = IoCManager.Resolve<IGameTicker>(); var ticker = EntitySystem.Get<GameTicker>();
if (ticker.RunLevel != GameRunLevel.InRound) if (ticker.RunLevel != GameRunLevel.InRound)
{ {

View File

@@ -1,6 +1,7 @@
using Content.Server.Administration; using Content.Server.Administration;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Server.GameTicking.Commands namespace Content.Server.GameTicking.Commands
@@ -14,7 +15,7 @@ namespace Content.Server.GameTicking.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args) public void Execute(IConsoleShell shell, string argStr, string[] args)
{ {
var ticker = IoCManager.Resolve<IGameTicker>(); var ticker = EntitySystem.Get<GameTicker>();
if (ticker.RunLevel != GameRunLevel.PreRoundLobby) if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
{ {
shell.WriteLine("This can only be executed while the game is in the pre-round lobby."); shell.WriteLine("This can only be executed while the game is in the pre-round lobby.");

View File

@@ -6,6 +6,7 @@ using Content.Shared.Administration;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Server.GameTicking.Commands namespace Content.Server.GameTicking.Commands
@@ -21,7 +22,7 @@ namespace Content.Server.GameTicking.Commands
Type? preset = null; Type? preset = null;
var presetName = string.Join(" ", args); var presetName = string.Join(" ", args);
var ticker = IoCManager.Resolve<IGameTicker>(); var ticker = EntitySystem.Get<GameTicker>();
if (args.Length > 0) if (args.Length > 0)
{ {

View File

@@ -3,6 +3,7 @@ using Content.Server.Administration;
using Content.Shared.Roles; using Content.Shared.Roles;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -30,7 +31,7 @@ namespace Content.Server.GameTicking.Commands
return; return;
} }
var ticker = IoCManager.Resolve<IGameTicker>(); var ticker = EntitySystem.Get<GameTicker>();
if (ticker.RunLevel == GameRunLevel.PreRoundLobby) if (ticker.RunLevel == GameRunLevel.PreRoundLobby)
{ {
shell.WriteLine("Round has not started."); shell.WriteLine("Round has not started.");

View File

@@ -2,6 +2,7 @@
using Content.Server.Administration; using Content.Server.Administration;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Server.GameTicking.Commands namespace Content.Server.GameTicking.Commands
@@ -15,7 +16,7 @@ namespace Content.Server.GameTicking.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args) public void Execute(IConsoleShell shell, string argStr, string[] args)
{ {
var ticker = IoCManager.Resolve<IGameTicker>(); var ticker = EntitySystem.Get<GameTicker>();
ticker.RestartRound(); ticker.RestartRound();
} }
} }

View File

@@ -1,6 +1,7 @@
using Content.Server.Administration; using Content.Server.Administration;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Server.GameTicking.Commands namespace Content.Server.GameTicking.Commands
@@ -20,7 +21,7 @@ namespace Content.Server.GameTicking.Commands
return; return;
} }
var ticker = IoCManager.Resolve<IGameTicker>(); var ticker = EntitySystem.Get<GameTicker>();
ticker.MakeObserve(player); ticker.MakeObserve(player);
} }
} }

View File

@@ -1,6 +1,7 @@
using Content.Server.Players; using Content.Server.Players;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Network; using Robust.Shared.Network;
@@ -22,7 +23,7 @@ namespace Content.Server.GameTicking.Commands
} }
var playerMgr = IoCManager.Resolve<IPlayerManager>(); var playerMgr = IoCManager.Resolve<IPlayerManager>();
var ticker = IoCManager.Resolve<IGameTicker>(); var ticker = EntitySystem.Get<GameTicker>();
NetUserId userId; NetUserId userId;
if (args.Length == 0) if (args.Length == 0)

View File

@@ -1,6 +1,7 @@
using Content.Server.Administration; using Content.Server.Administration;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Server.GameTicking.Commands namespace Content.Server.GameTicking.Commands
@@ -20,7 +21,7 @@ namespace Content.Server.GameTicking.Commands
return; return;
} }
var ticker = IoCManager.Resolve<IGameTicker>(); var ticker = EntitySystem.Get<GameTicker>();
ticker.SetStartPreset(args[0]); ticker.SetStartPreset(args[0]);
} }

View File

@@ -2,6 +2,7 @@
using Content.Server.Administration; using Content.Server.Administration;
using Content.Shared.Administration; using Content.Shared.Administration;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Server.GameTicking.Commands namespace Content.Server.GameTicking.Commands
@@ -15,7 +16,7 @@ namespace Content.Server.GameTicking.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args) public void Execute(IConsoleShell shell, string argStr, string[] args)
{ {
var ticker = IoCManager.Resolve<IGameTicker>(); var ticker = EntitySystem.Get<GameTicker>();
if (ticker.RunLevel != GameRunLevel.PreRoundLobby) if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
{ {

View File

@@ -1,6 +1,9 @@
using Content.Server.Administration; using Content.Server.Administration;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.CCVar;
using Robust.Shared.Configuration;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Server.GameTicking.Commands namespace Content.Server.GameTicking.Commands
@@ -20,11 +23,11 @@ namespace Content.Server.GameTicking.Commands
return; return;
} }
var ticker = IoCManager.Resolve<IGameTicker>(); var cfgMan = IoCManager.Resolve<IConfigurationManager>();
if (bool.TryParse(args[0], out var result)) if (bool.TryParse(args[0], out var result))
{ {
ticker.ToggleDisallowLateJoin(bool.Parse(args[0])); cfgMan.SetCVar(CCVars.GameDisallowLateJoins, bool.Parse(args[0]));
shell.WriteLine(result ? "Late joining has been disabled." : "Late joining has been enabled."); shell.WriteLine(result ? "Late joining has been disabled." : "Late joining has been enabled.");
} }
else else

View File

@@ -1,6 +1,7 @@
using Content.Server.Administration; using Content.Server.Administration;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Server.GameTicking.Commands namespace Content.Server.GameTicking.Commands
@@ -20,7 +21,7 @@ namespace Content.Server.GameTicking.Commands
return; return;
} }
var ticker = IoCManager.Resolve<IGameTicker>(); var ticker = EntitySystem.Get<GameTicker>();
ticker.ToggleReady(player, bool.Parse(args[0])); ticker.ToggleReady(player, bool.Parse(args[0]));
} }
} }

View File

@@ -0,0 +1,34 @@
using System;
using Content.Shared.CCVar;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameTicking
{
public partial class GameTicker
{
[ViewVariables]
public bool LobbyEnabled { get; private set; } = false;
[ViewVariables]
public bool DummyTicker { get; private set; } = false;
[ViewVariables]
public string ChosenMap { get; private set; } = string.Empty;
[ViewVariables]
public TimeSpan LobbyDuration { get; private set; } = TimeSpan.Zero;
[ViewVariables]
public bool DisallowLateJoin { get; private set; } = false;
private void InitializeCVars()
{
_configurationManager.OnValueChanged(CCVars.GameLobbyEnabled, value => LobbyEnabled = value, true);
_configurationManager.OnValueChanged(CCVars.GameDummyTicker, value => DummyTicker = value, true);
_configurationManager.OnValueChanged(CCVars.GameMap, value => ChosenMap = value, true);
_configurationManager.OnValueChanged(CCVars.GameLobbyDuration, value => LobbyDuration = TimeSpan.FromSeconds(value), true);
_configurationManager.OnValueChanged(CCVars.GameDisallowLateJoins, invokeImmediately:true,
onValueChanged:value => { DisallowLateJoin = value; UpdateLateJoinStatus(); UpdateJobsAvailable(); });
}
}
}

View File

@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Content.Server.GameTicking.Presets;
using Content.Shared.CCVar;
using Content.Shared.Preferences;
using Robust.Shared.Network;
using Robust.Shared.ViewVariables;
using Robust.Shared.IoC;
namespace Content.Server.GameTicking
{
public partial class GameTicker
{
public const float PresetFailedCooldownIncrease = 30f;
[ViewVariables] private Type? _presetType;
[ViewVariables]
public GamePreset? Preset
{
get => _preset ?? MakeGamePreset(new Dictionary<NetUserId, HumanoidCharacterProfile>());
set => _preset = value;
}
public ImmutableDictionary<string, Type> Presets { get; private set; } = default!;
private GamePreset? _preset;
private void InitializeGamePreset()
{
var presets = new Dictionary<string, Type>();
foreach (var type in _reflectionManager.FindTypesWithAttribute<GamePresetAttribute>())
{
var attribute = type.GetCustomAttribute<GamePresetAttribute>();
presets.Add(attribute!.Id.ToLowerInvariant(), type);
foreach (var alias in attribute.Aliases)
{
presets.Add(alias.ToLowerInvariant(), type);
}
}
Presets = presets.ToImmutableDictionary();
SetStartPreset(_configurationManager.GetCVar(CCVars.GameLobbyDefaultPreset));
}
public bool OnGhostAttempt(Mind.Mind mind, bool canReturnGlobal)
{
return Preset?.OnGhostAttempt(mind, canReturnGlobal) ?? false;
}
public bool TryGetPreset(string name, [NotNullWhen(true)] out Type? type)
{
name = name.ToLowerInvariant();
return Presets.TryGetValue(name, out type);
}
public void SetStartPreset(Type type, bool force = false)
{
// Do nothing if this game ticker is a dummy!
if (DummyTicker)
return;
if (!typeof(GamePreset).IsAssignableFrom(type)) throw new ArgumentException("type must inherit GamePreset");
_presetType = type;
UpdateInfoText();
if (force)
{
StartRound(true);
}
}
public void SetStartPreset(string name, bool force = false)
{
if (!TryGetPreset(name, out var type))
{
throw new NotSupportedException($"No preset found with name {name}");
}
SetStartPreset(type, force);
}
private GamePreset MakeGamePreset(Dictionary<NetUserId, HumanoidCharacterProfile> readyProfiles)
{
var preset = _dynamicTypeFactory.CreateInstance<GamePreset>(_presetType ?? typeof(PresetSandbox));
preset.ReadyProfiles = readyProfiles;
return preset;
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using Content.Server.GameTicking.Rules;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameTicking
{
public partial class GameTicker
{
[ViewVariables] private readonly List<GameRule> _gameRules = new();
public IEnumerable<GameRule> ActiveGameRules => _gameRules;
public T AddGameRule<T>() where T : GameRule, new()
{
var instance = _dynamicTypeFactory.CreateInstance<T>();
_gameRules.Add(instance);
instance.Added();
RaiseLocalEvent(new GameRuleAddedEvent(instance));
return instance;
}
public bool HasGameRule(string? name)
{
if (name == null)
return false;
foreach (var rule in _gameRules)
{
if (rule.GetType().Name == name)
{
return true;
}
}
return false;
}
public bool HasGameRule(Type? type)
{
if (type == null || !typeof(GameRule).IsAssignableFrom(type))
return false;
foreach (var rule in _gameRules)
{
if (rule.GetType().IsAssignableFrom(type))
return true;
}
return false;
}
public void RemoveGameRule(GameRule rule)
{
if (_gameRules.Contains(rule)) return;
rule.Removed();
_gameRules.Remove(rule);
}
}
public class GameRuleAddedEvent
{
public GameRule Rule { get; }
public GameRuleAddedEvent(GameRule rule)
{
Rule = rule;
}
}
}

View File

@@ -3,11 +3,13 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Content.Shared.GameTicking;
using Content.Shared.Preferences; using Content.Shared.Preferences;
using Content.Shared.Roles; using Content.Shared.Roles;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -17,6 +19,9 @@ namespace Content.Server.GameTicking
// This code is responsible for the assigning & picking of jobs. // This code is responsible for the assigning & picking of jobs.
public partial class GameTicker public partial class GameTicker
{ {
[ViewVariables]
private readonly List<ManifestEntry> _manifest = new();
[ViewVariables] [ViewVariables]
private readonly Dictionary<string, int> _spawnedPositions = new(); private readonly Dictionary<string, int> _spawnedPositions = new();
@@ -187,7 +192,7 @@ namespace Content.Server.GameTicking
} }
[Conditional("DEBUG")] [Conditional("DEBUG")]
private void JobControllerInit() private void InitializeJobController()
{ {
// Verify that the overflow role exists and has the correct name. // Verify that the overflow role exists and has the correct name.
var role = _prototypeManager.Index<JobPrototype>(OverflowJob); var role = _prototypeManager.Index<JobPrototype>(OverflowJob);
@@ -202,10 +207,23 @@ namespace Content.Server.GameTicking
_spawnedPositions[jobId] = _spawnedPositions.GetValueOrDefault(jobId, 0) + 1; _spawnedPositions[jobId] = _spawnedPositions.GetValueOrDefault(jobId, 0) + 1;
} }
private TickerJobsAvailableEvent GetJobsAvailable()
{
// If late join is disallowed, return no available jobs.
if (DisallowLateJoin)
return new TickerJobsAvailableEvent(Array.Empty<string>());
var jobs = GetAvailablePositions()
.Where(e => e.Value > 0)
.Select(e => e.Key)
.ToArray();
return new TickerJobsAvailableEvent(jobs);
}
private void UpdateJobsAvailable() private void UpdateJobsAvailable()
{ {
var lobbyPlayers = _playersInLobby.Keys.Select(p => p.ConnectedClient).ToList(); RaiseNetworkEvent(GetJobsAvailable(), Filter.Empty().AddPlayers(_playersInLobby.Keys));
_netManager.ServerSendToMany(GetJobsAvailable(), lobbyPlayers);
} }
} }
} }

View File

@@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.GameTicking;
using Robust.Server.Player;
using Robust.Shared.Localization;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameTicking
{
public partial class GameTicker
{
[ViewVariables]
private readonly Dictionary<IPlayerSession, LobbyPlayerStatus> _playersInLobby = new();
[ViewVariables]
private TimeSpan _roundStartTime;
[ViewVariables]
private TimeSpan _pauseTime;
[ViewVariables]
public bool Paused { get; set; }
[ViewVariables]
private bool _roundStartCountdownHasNotStartedYetDueToNoPlayers;
private void UpdateInfoText()
{
RaiseNetworkEvent(GetInfoMsg(), Filter.Empty().AddPlayers(_playersInLobby.Keys));
}
private string GetInfoText()
{
if (Preset == null)
{
return string.Empty;
}
var gmTitle = Preset.ModeTitle;
var desc = Preset.Description;
return Loc.GetString(@"Hi and welcome to [color=white]Space Station 14![/color]
The current game mode is: [color=white]{0}[/color].
[color=yellow]{1}[/color]", gmTitle, desc);
}
private TickerLobbyReadyEvent GetStatusSingle(ICommonSession player, LobbyPlayerStatus status)
{
return new (new Dictionary<NetUserId, LobbyPlayerStatus> { { player.UserId, status } });
}
private TickerLobbyReadyEvent GetPlayerStatus()
{
var players = new Dictionary<NetUserId, LobbyPlayerStatus>();
foreach (var player in _playersInLobby.Keys)
{
_playersInLobby.TryGetValue(player, out var status);
players.Add(player.UserId, status);
}
return new TickerLobbyReadyEvent(players);
}
private TickerLobbyStatusEvent GetStatusMsg(IPlayerSession session)
{
_playersInLobby.TryGetValue(session, out var status);
return new TickerLobbyStatusEvent(RunLevel != GameRunLevel.PreRoundLobby, LobbySong, status == LobbyPlayerStatus.Ready, _roundStartTime, Paused);
}
private void SendStatusToAll()
{
foreach (var player in _playersInLobby.Keys)
{
RaiseNetworkEvent(GetStatusMsg(player), player.ConnectedClient);
}
}
private TickerLobbyInfoEvent GetInfoMsg()
{
return new (GetInfoText());
}
private void UpdateLateJoinStatus()
{
RaiseNetworkEvent(new TickerLateJoinStatusEvent(DisallowLateJoin));
}
public bool PauseStart(bool pause = true)
{
if (Paused == pause)
{
return false;
}
Paused = pause;
if (pause)
{
_pauseTime = _gameTiming.CurTime;
}
else if (_pauseTime != default)
{
_roundStartTime += _gameTiming.CurTime - _pauseTime;
}
RaiseNetworkEvent(new TickerLobbyCountdownEvent(_roundStartTime, Paused));
_chatManager.DispatchServerAnnouncement(Paused
? "Round start has been paused."
: "Round start countdown is now resumed.");
return true;
}
public bool TogglePause()
{
PauseStart(!Paused);
return Paused;
}
public void ToggleReady(IPlayerSession player, bool ready)
{
if (!_playersInLobby.ContainsKey(player)) return;
if (!_prefsManager.HavePreferencesLoaded(player))
{
return;
}
var status = ready ? LobbyPlayerStatus.Ready : LobbyPlayerStatus.NotReady;
_playersInLobby[player] = ready ? LobbyPlayerStatus.Ready : LobbyPlayerStatus.NotReady;
RaiseNetworkEvent(GetStatusMsg(player), player.ConnectedClient);
RaiseNetworkEvent(GetStatusSingle(player, status));
}
}
}

View File

@@ -0,0 +1,78 @@
using Content.Shared.Audio;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameTicking
{
public partial class GameTicker
{
[Dependency] private readonly IResourceManager _resourceManager = default!;
private const string LobbyMusicCollection = "LobbyMusic";
[ViewVariables]
private bool _lobbyMusicInitialized = false;
[ViewVariables]
private SoundCollectionPrototype _lobbyMusicCollection = default!;
[ViewVariables]
public string? LobbySong { get; private set; }
private void InitializeLobbyMusic()
{
DebugTools.Assert(!_lobbyMusicInitialized);
_lobbyMusicCollection = _prototypeManager.Index<SoundCollectionPrototype>(LobbyMusicCollection);
// Now that the collection is set, the lobby music has been initialized and we can choose a random song.
_lobbyMusicInitialized = true;
ChooseRandomLobbySong();
}
/// <summary>
/// Sets the current lobby song, or stops it if null.
/// </summary>
/// <param name="song">The lobby song to play, or null to stop any lobby songs.</param>
public void SetLobbySong(string? song)
{
DebugTools.Assert(_lobbyMusicInitialized);
if (song == null)
{
LobbySong = null;
return;
// TODO GAMETICKER send song stop event
}
if (!_resourceManager.ContentFileExists(song))
{
Logger.ErrorS("ticker", $"Tried to set lobby song to \"{song}\", which doesn't exist!");
return;
}
LobbySong = song;
// TODO GAMETICKER send song change event
}
/// <summary>
/// Plays a random song from the LobbyMusic sound collection.
/// </summary>
public void ChooseRandomLobbySong()
{
DebugTools.Assert(_lobbyMusicInitialized);
SetLobbySong(_robustRandom.Pick(_lobbyMusicCollection.PickFiles));
}
/// <summary>
/// Stops the current lobby song being played.
/// </summary>
public void StopLobbySong()
{
SetLobbySong(null);
}
}
}

View File

@@ -0,0 +1,144 @@
using Content.Server.Players;
using Content.Shared.GameTicking;
using Content.Shared.GameWindow;
using Content.Shared.Preferences;
using JetBrains.Annotations;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.GameTicking
{
[UsedImplicitly]
public partial class GameTicker
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
private void InitializePlayer()
{
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
}
private void PlayerStatusChanged(object? sender, SessionStatusEventArgs args)
{
var session = args.Session;
switch (args.NewStatus)
{
case SessionStatus.Connecting:
// Cancel shutdown update timer in progress.
_updateShutdownCts?.Cancel();
break;
case SessionStatus.Connected:
{
// Always make sure the client has player data. Mind gets assigned on spawn.
if (session.Data.ContentDataUncast == null)
session.Data.ContentDataUncast = new PlayerData(session.UserId);
// Make the player actually join the game.
// timer time must be > tick length
Timer.Spawn(0, args.Session.JoinGame);
_chatManager.SendAdminAnnouncement(Loc.GetString("player-join-message", ("name", args.Session.Name)));
if (LobbyEnabled && _roundStartCountdownHasNotStartedYetDueToNoPlayers)
{
_roundStartCountdownHasNotStartedYetDueToNoPlayers = false;
_roundStartTime = _gameTiming.CurTime + LobbyDuration;
}
break;
}
case SessionStatus.InGame:
{
_prefsManager.OnClientConnected(session);
var data = session.ContentData();
DebugTools.AssertNotNull(data);
if (data!.Mind == null)
{
if (LobbyEnabled)
{
PlayerJoinLobby(session);
return;
}
SpawnWaitPrefs();
}
else
{
if (data.Mind.CurrentEntity == null)
{
SpawnWaitPrefs();
}
else
{
session.AttachToEntity(data.Mind.CurrentEntity);
PlayerJoinGame(session);
}
}
break;
}
case SessionStatus.Disconnected:
{
if (_playersInLobby.ContainsKey(session)) _playersInLobby.Remove(session);
_chatManager.SendAdminAnnouncement(Loc.GetString("player-leave-message", ("name", args.Session.Name)));
ServerEmptyUpdateRestartCheck();
_prefsManager.OnClientDisconnected(session);
break;
}
}
async void SpawnWaitPrefs()
{
await _prefsManager.WaitPreferencesLoaded(session);
SpawnPlayer(session);
}
}
private HumanoidCharacterProfile GetPlayerProfile(IPlayerSession p)
{
return (HumanoidCharacterProfile) _prefsManager.GetPreferences(p.UserId).SelectedCharacter;
}
private void PlayerJoinGame(IPlayerSession session)
{
_chatManager.DispatchServerMessage(session,
"Welcome to Space Station 14! If this is your first time checking out the game, be sure to check out the tutorial in the top left!");
if (_playersInLobby.ContainsKey(session))
_playersInLobby.Remove(session);
RaiseNetworkEvent(new TickerJoinGameEvent(), session.ConnectedClient);
}
private void PlayerJoinLobby(IPlayerSession session)
{
_playersInLobby[session] = LobbyPlayerStatus.NotReady;
var client = session.ConnectedClient;
RaiseNetworkEvent(new TickerJoinLobbyEvent(), client);
RaiseNetworkEvent(GetStatusMsg(session), client);
RaiseNetworkEvent(GetInfoMsg(), client);
RaiseNetworkEvent(GetPlayerStatus(), client);
RaiseNetworkEvent(GetJobsAvailable(), client);
}
private void ReqWindowAttentionAll()
{
RaiseNetworkEvent(new RequestWindowAttentionEvent());
}
}
}

View File

@@ -0,0 +1,363 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Server.Players;
using Content.Shared.CCVar;
using Content.Shared.Coordinates;
using Content.Shared.GameTicking;
using Content.Shared.Preferences;
using Prometheus;
using Robust.Server.Player;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameTicking
{
public partial class GameTicker
{
private static readonly Counter RoundNumberMetric = Metrics.CreateCounter(
"ss14_round_number",
"Round number.");
private static readonly Gauge RoundLengthMetric = Metrics.CreateGauge(
"ss14_round_length",
"Round length in seconds.");
[ViewVariables]
private TimeSpan _roundStartTimeSpan;
[ViewVariables]
private GameRunLevel _runLevel;
[ViewVariables]
public GameRunLevel RunLevel
{
get => _runLevel;
private set
{
if (_runLevel == value) return;
var old = _runLevel;
_runLevel = value;
RaiseLocalEvent(new GameRunLevelChangedEvent(old, value));
}
}
private void PreRoundSetup()
{
DefaultMap = _mapManager.CreateMap();
var startTime = _gameTiming.RealTime;
var map = ChosenMap;
var grid = _mapLoader.LoadBlueprint(DefaultMap, map);
if (grid == null)
{
throw new InvalidOperationException($"No grid found for map {map}");
}
DefaultGridId = grid.Index;
_spawnPoint = grid.ToCoordinates();
var timeSpan = _gameTiming.RealTime - startTime;
Logger.InfoS("ticker", $"Loaded map in {timeSpan.TotalMilliseconds:N2}ms.");
}
public void StartRound(bool force = false)
{
// If this game ticker is a dummy, do nothing!
if (DummyTicker)
return;
DebugTools.Assert(RunLevel == GameRunLevel.PreRoundLobby);
Logger.InfoS("ticker", "Starting round!");
SendServerMessage("The round is starting now...");
List<IPlayerSession> readyPlayers;
if (LobbyEnabled)
{
readyPlayers = _playersInLobby.Where(p => p.Value == LobbyPlayerStatus.Ready).Select(p => p.Key).ToList();
}
else
{
readyPlayers = _playersInLobby.Keys.ToList();
}
RunLevel = GameRunLevel.InRound;
RoundLengthMetric.Set(0);
// Get the profiles for each player for easier lookup.
var profiles = _prefsManager.GetSelectedProfilesForPlayers(
readyPlayers
.Select(p => p.UserId).ToList())
.ToDictionary(p => p.Key, p => (HumanoidCharacterProfile) p.Value);
foreach (var readyPlayer in readyPlayers)
{
if (!profiles.ContainsKey(readyPlayer.UserId))
{
profiles.Add(readyPlayer.UserId, HumanoidCharacterProfile.Random());
}
}
var assignedJobs = AssignJobs(readyPlayers, profiles);
// For players without jobs, give them the overflow job if they have that set...
foreach (var player in readyPlayers)
{
if (assignedJobs.ContainsKey(player))
{
continue;
}
var profile = profiles[player.UserId];
if (profile.PreferenceUnavailable == PreferenceUnavailableMode.SpawnAsOverflow)
{
assignedJobs.Add(player, OverflowJob);
}
}
// Spawn everybody in!
foreach (var (player, job) in assignedJobs)
{
SpawnPlayer(player, profiles[player.UserId], job, false);
}
// Time to start the preset.
Preset = MakeGamePreset(profiles);
DisallowLateJoin |= Preset.DisallowLateJoin;
if (!Preset.Start(assignedJobs.Keys.ToList(), force))
{
if (_configurationManager.GetCVar(CCVars.GameLobbyFallbackEnabled))
{
SetStartPreset(_configurationManager.GetCVar(CCVars.GameLobbyFallbackPreset));
var newPreset = MakeGamePreset(profiles);
_chatManager.DispatchServerAnnouncement(
$"Failed to start {Preset.ModeTitle} mode! Defaulting to {newPreset.ModeTitle}...");
if (!newPreset.Start(readyPlayers, force))
{
throw new ApplicationException("Fallback preset failed to start!");
}
DisallowLateJoin = false;
DisallowLateJoin |= newPreset.DisallowLateJoin;
Preset = newPreset;
}
else
{
SendServerMessage($"Failed to start {Preset.ModeTitle} mode! Restarting round...");
RestartRound();
DelayStart(TimeSpan.FromSeconds(PresetFailedCooldownIncrease));
return;
}
}
Preset.OnGameStarted();
_roundStartTimeSpan = IoCManager.Resolve<IGameTiming>().RealTime;
SendStatusToAll();
ReqWindowAttentionAll();
UpdateLateJoinStatus();
UpdateJobsAvailable();
}
public void EndRound(string text = "")
{
// If this game ticker is a dummy, do nothing!
if (DummyTicker)
return;
DebugTools.Assert(RunLevel == GameRunLevel.InRound);
Logger.InfoS("ticker", "Ending round!");
RunLevel = GameRunLevel.PostRound;
//Tell every client the round has ended.
var gamemodeTitle = Preset?.ModeTitle ?? string.Empty;
var roundEndText = text + $"\n{Preset?.GetRoundEndDescription() ?? string.Empty}";
//Get the timespan of the round.
var roundDuration = IoCManager.Resolve<IGameTiming>().RealTime.Subtract(_roundStartTimeSpan);
//Generate a list of basic player info to display in the end round summary.
var listOfPlayerInfo = new List<RoundEndMessageEvent.RoundEndPlayerInfo>();
foreach (var ply in _playerManager.GetAllPlayers().OrderBy(p => p.Name))
{
var mind = ply.ContentData()?.Mind;
if (mind != null)
{
_playersInLobby.TryGetValue(ply, out var status);
var antag = mind.AllRoles.Any(role => role.Antagonist);
var playerEndRoundInfo = new RoundEndMessageEvent.RoundEndPlayerInfo()
{
PlayerOOCName = ply.Name,
PlayerICName = mind.CurrentEntity?.Name,
Role = antag
? mind.AllRoles.First(role => role.Antagonist).Name
: mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("Unknown"),
Antag = antag,
Observer = status == LobbyPlayerStatus.Observer,
};
listOfPlayerInfo.Add(playerEndRoundInfo);
}
}
RaiseNetworkEvent(new RoundEndMessageEvent(gamemodeTitle, roundEndText, roundDuration, listOfPlayerInfo.Count, listOfPlayerInfo.ToArray()));
}
public void RestartRound()
{
// If this game ticker is a dummy, do nothing!
if (DummyTicker)
return;
if (_updateOnRoundEnd)
{
_baseServer.Shutdown(
Loc.GetString("Server is shutting down for update and will automatically restart."));
return;
}
Logger.InfoS("ticker", "Restarting round!");
SendServerMessage("Restarting round...");
RoundNumberMetric.Inc();
RunLevel = GameRunLevel.PreRoundLobby;
LobbySong = _robustRandom.Pick(_lobbyMusicCollection.PickFiles);
ResettingCleanup();
PreRoundSetup();
if (!LobbyEnabled)
{
StartRound();
}
else
{
Preset = null;
if (_playerManager.PlayerCount == 0)
_roundStartCountdownHasNotStartedYetDueToNoPlayers = true;
else
_roundStartTime = _gameTiming.CurTime + LobbyDuration;
SendStatusToAll();
ReqWindowAttentionAll();
}
}
/// <summary>
/// Cleanup that has to run to clear up anything from the previous round.
/// Stuff like wiping the previous map clean.
/// </summary>
private void ResettingCleanup()
{
// Move everybody currently in the server to lobby.
foreach (var player in _playerManager.GetAllPlayers())
{
PlayerJoinLobby(player);
}
// Delete the minds of everybody.
// TODO: Maybe move this into a separate manager?
foreach (var unCastData in _playerManager.GetAllPlayerData())
{
unCastData.ContentData()?.WipeMind();
}
// Delete all entities.
foreach (var entity in _entityManager.GetEntities().ToList())
{
// TODO: Maybe something less naive here?
// FIXME: Actually, definitely.
entity.Delete();
}
_mapManager.Restart();
// Clear up any game rules.
foreach (var rule in _gameRules)
{
rule.Removed();
}
_gameRules.Clear();
foreach (var system in _entitySystemManager.AllSystems)
{
if (system is IResettingEntitySystem resetting)
{
resetting.Reset();
}
}
_spawnedPositions.Clear();
_manifest.Clear();
DisallowLateJoin = false;
}
public bool DelayStart(TimeSpan time)
{
if (_runLevel != GameRunLevel.PreRoundLobby)
{
return false;
}
_roundStartTime += time;
RaiseNetworkEvent(new TickerLobbyCountdownEvent(_roundStartTime, Paused));
_chatManager.DispatchServerAnnouncement($"Round start has been delayed for {time.TotalSeconds} seconds.");
return true;
}
private void UpdateRoundFlow(float frameTime)
{
if (RunLevel == GameRunLevel.InRound)
{
RoundLengthMetric.Inc(frameTime);
}
if (RunLevel != GameRunLevel.PreRoundLobby ||
Paused ||
_roundStartTime > _gameTiming.CurTime ||
_roundStartCountdownHasNotStartedYetDueToNoPlayers)
{
return;
}
StartRound();
}
}
public enum GameRunLevel
{
PreRoundLobby = 0,
InRound = 1,
PostRound = 2
}
public class GameRunLevelChangedEvent
{
public GameRunLevel Old { get; }
public GameRunLevel New { get; }
public GameRunLevelChangedEvent(GameRunLevel old, GameRunLevel @new)
{
Old = old;
New = @new;
}
}
}

View File

@@ -0,0 +1,296 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Content.Server.Access.Components;
using Content.Server.CharacterAppearance.Components;
using Content.Server.Ghost.Components;
using Content.Server.Hands.Components;
using Content.Server.Inventory.Components;
using Content.Server.Items;
using Content.Server.PDA;
using Content.Server.Players;
using Content.Server.Roles;
using Content.Server.Spawners.Components;
using Content.Server.Speech.Components;
using Content.Shared.GameTicking;
using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameTicking
{
public partial class GameTicker
{
private const string PlayerPrototypeName = "HumanMob_Content";
private const string ObserverPrototypeName = "MobObserver";
[ViewVariables(VVAccess.ReadWrite)]
private EntityCoordinates _spawnPoint;
// Mainly to avoid allocations.
private readonly List<EntityCoordinates> _possiblePositions = new();
private void SpawnPlayer(IPlayerSession player, string? jobId = null, bool lateJoin = true)
{
var character = GetPlayerProfile(player);
SpawnPlayer(player, character, jobId, lateJoin);
UpdateJobsAvailable();
}
private void SpawnPlayer(IPlayerSession player, HumanoidCharacterProfile character, string? jobId = null, bool lateJoin = true)
{
// Can't spawn players with a dummy ticker!
if (DummyTicker)
return;
if (lateJoin && DisallowLateJoin)
{
MakeObserve(player);
return;
}
PlayerJoinGame(player);
var data = player.ContentData();
DebugTools.AssertNotNull(data);
data!.WipeMind();
data.Mind = new Mind.Mind(player.UserId)
{
CharacterName = character.Name
};
// Pick best job best on prefs.
jobId ??= PickBestAvailableJob(character);
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
var job = new Job(data.Mind, jobPrototype);
data.Mind.AddRole(job);
if (lateJoin)
{
_chatManager.DispatchStationAnnouncement(Loc.GetString(
"latejoin-arrival-announcement",
("character", character.Name),
("job", CultureInfo.CurrentCulture.TextInfo.ToTitleCase(job.Name))
), Loc.GetString("latejoin-arrival-sender"));
}
var mob = SpawnPlayerMob(job, character, lateJoin);
data.Mind.TransferTo(mob);
if (player.UserId == new Guid("{e887eb93-f503-4b65-95b6-2f282c014192}"))
{
mob.AddComponent<OwOAccentComponent>();
}
AddManifestEntry(character.Name, jobId);
AddSpawnedPosition(jobId);
EquipIdCard(mob, character.Name, jobPrototype);
jobPrototype.Special?.AfterEquip(mob);
Preset?.OnSpawnPlayerCompleted(player, mob, lateJoin);
}
public void Respawn(IPlayerSession player)
{
player.ContentData()?.WipeMind();
if (LobbyEnabled)
PlayerJoinLobby(player);
else
SpawnPlayer(player);
}
public void MakeJoinGame(IPlayerSession player, string? jobId = null)
{
if (!_playersInLobby.ContainsKey(player)) return;
if (!_prefsManager.HavePreferencesLoaded(player))
{
return;
}
SpawnPlayer(player, jobId);
}
public void MakeObserve(IPlayerSession player)
{
// Can't spawn players with a dummy ticker!
if (DummyTicker)
return;
if (!_playersInLobby.ContainsKey(player)) return;
PlayerJoinGame(player);
var name = GetPlayerProfile(player).Name;
var data = player.ContentData();
DebugTools.AssertNotNull(data);
data!.WipeMind();
data.Mind = new Mind.Mind(player.UserId);
var mob = SpawnObserverMob();
mob.Name = name;
mob.GetComponent<GhostComponent>().CanReturnToBody = false;
data.Mind.TransferTo(mob);
_playersInLobby[player] = LobbyPlayerStatus.Observer;
RaiseNetworkEvent(GetStatusSingle(player, LobbyPlayerStatus.Observer));
}
#region Mob Spawning Helpers
private IEntity SpawnPlayerMob(Job job, HumanoidCharacterProfile? profile, bool lateJoin = true)
{
var coordinates = lateJoin ? GetLateJoinSpawnPoint() : GetJobSpawnPoint(job.Prototype.ID);
var entity = _entityManager.SpawnEntity(PlayerPrototypeName, coordinates);
if (job.StartingGear != null)
{
var startingGear = _prototypeManager.Index<StartingGearPrototype>(job.StartingGear);
EquipStartingGear(entity, startingGear, profile);
}
if (profile != null)
{
entity.GetComponent<HumanoidAppearanceComponent>().UpdateFromProfile(profile);
entity.Name = profile.Name;
}
return entity;
}
private IEntity SpawnObserverMob()
{
var coordinates = GetObserverSpawnPoint();
return _entityManager.SpawnEntity(ObserverPrototypeName, coordinates);
}
#endregion
#region Equip Helpers
public void EquipStartingGear(IEntity entity, StartingGearPrototype startingGear, HumanoidCharacterProfile? profile)
{
if (entity.TryGetComponent(out InventoryComponent? inventory))
{
foreach (var slot in EquipmentSlotDefines.AllSlots)
{
var equipmentStr = startingGear.GetGear(slot, profile);
if (equipmentStr != "")
{
var equipmentEntity = _entityManager.SpawnEntity(equipmentStr, entity.Transform.Coordinates);
inventory.Equip(slot, equipmentEntity.GetComponent<ItemComponent>());
}
}
}
if (entity.TryGetComponent(out HandsComponent? handsComponent))
{
var inhand = startingGear.Inhand;
foreach (var (hand, prototype) in inhand)
{
var inhandEntity = _entityManager.SpawnEntity(prototype, entity.Transform.Coordinates);
handsComponent.PutInHand(inhandEntity.GetComponent<ItemComponent>(), hand);
}
}
}
public void EquipIdCard(IEntity entity, string characterName, JobPrototype jobPrototype)
{
if (!entity.TryGetComponent(out InventoryComponent? inventory))
return;
if (!inventory.TryGetSlotItem(EquipmentSlotDefines.Slots.IDCARD, out ItemComponent? item))
{
return;
}
var itemEntity = item.Owner;
if (!itemEntity.TryGetComponent(out PDAComponent? pdaComponent) || pdaComponent.ContainedID == null)
return;
var card = pdaComponent.ContainedID;
card.FullName = characterName;
card.JobTitle = jobPrototype.Name;
var access = card.Owner.GetComponent<AccessComponent>();
var accessTags = access.Tags;
accessTags.UnionWith(jobPrototype.Access);
pdaComponent.SetPDAOwner(characterName);
}
#endregion
private void AddManifestEntry(string characterName, string jobId)
{
_manifest.Add(new ManifestEntry(characterName, jobId));
}
#region Spawn Points
public EntityCoordinates GetJobSpawnPoint(string jobId)
{
var location = _spawnPoint;
_possiblePositions.Clear();
foreach (var (point, transform) in ComponentManager.EntityQuery<SpawnPointComponent, ITransformComponent>())
{
if (point.SpawnType == SpawnPointType.Job && point.Job?.ID == jobId)
_possiblePositions.Add(transform.Coordinates);
}
if (_possiblePositions.Count != 0)
location = _robustRandom.Pick(_possiblePositions);
return location;
}
public EntityCoordinates GetLateJoinSpawnPoint()
{
var location = _spawnPoint;
_possiblePositions.Clear();
foreach (var (point, transform) in ComponentManager.EntityQuery<SpawnPointComponent, ITransformComponent>())
{
if (point.SpawnType == SpawnPointType.LateJoin) _possiblePositions.Add(transform.Coordinates);
}
if (_possiblePositions.Count != 0)
location = _robustRandom.Pick(_possiblePositions);
return location;
}
public EntityCoordinates GetObserverSpawnPoint()
{
var location = _spawnPoint;
_possiblePositions.Clear();
foreach (var (point, transform) in ComponentManager.EntityQuery<SpawnPointComponent, ITransformComponent>())
{
if (point.SpawnType == SpawnPointType.Observer)
_possiblePositions.Add(transform.Coordinates);
}
if (_possiblePositions.Count != 0)
location = _robustRandom.Pick(_possiblePositions);
return location;
}
#endregion
}
}

View File

@@ -0,0 +1,34 @@
using Newtonsoft.Json.Linq;
using Robust.Server.ServerStatus;
using Robust.Shared.IoC;
namespace Content.Server.GameTicking
{
public partial class GameTicker
{
/// <summary>
/// Used for thread safety, given <see cref="IStatusHost.OnStatusRequest"/> is called from another thread.
/// </summary>
private readonly object _statusShellLock = new();
private void InitializeStatusShell()
{
IoCManager.Resolve<IStatusHost>().OnStatusRequest += GetStatusResponse;
}
private void GetStatusResponse(JObject jObject)
{
// This method is raised from another thread, so this better be thread safe!
lock (_statusShellLock)
{
jObject["name"] = _baseServer.ServerName;
jObject["players"] = _playerManager.PlayerCount;
jObject["run_level"] = (int) _runLevel;
if (_runLevel >= GameRunLevel.InRound)
{
jObject["round_start_time"] = _roundStartTime.ToString("o");
}
}
}
}
}

View File

@@ -0,0 +1,63 @@
using System;
using System.Linq;
using System.Threading;
using Robust.Shared.Enums;
using Robust.Shared.Localization;
using Robust.Shared.ViewVariables;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Server.GameTicking
{
public partial class GameTicker
{
private static readonly TimeSpan UpdateRestartDelay = TimeSpan.FromSeconds(20);
[ViewVariables]
private bool _updateOnRoundEnd;
private CancellationTokenSource? _updateShutdownCts;
private void InitializeUpdates()
{
_watchdogApi.UpdateReceived += WatchdogApiOnUpdateReceived;
}
private void WatchdogApiOnUpdateReceived()
{
_chatManager.DispatchServerAnnouncement(Loc.GetString(
"Update has been received, server will automatically restart for update at the end of this round."));
_updateOnRoundEnd = true;
ServerEmptyUpdateRestartCheck();
}
/// <summary>
/// Checks whether there are still players on the server,
/// and if not starts a timer to automatically reboot the server if an update is available.
/// </summary>
private void ServerEmptyUpdateRestartCheck()
{
// Can't simple check the current connected player count since that doesn't update
// before PlayerStatusChanged gets fired.
// So in the disconnect handler we'd still see a single player otherwise.
var playersOnline = _playerManager.GetAllPlayers().Any(p => p.Status != SessionStatus.Disconnected);
if (playersOnline || !_updateOnRoundEnd)
{
// Still somebody online.
return;
}
if (_updateShutdownCts is {IsCancellationRequested: false})
{
// Do nothing because I guess we already have a timer running..?
return;
}
_updateShutdownCts = new CancellationTokenSource();
Timer.Spawn(UpdateRestartDelay, () =>
{
_baseServer.Shutdown(
Loc.GetString("Server is shutting down for update and will automatically restart."));
}, _updateShutdownCts.Token);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,40 +0,0 @@
using Content.Server.Players;
using Content.Shared.GameTicking;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
#nullable enable
namespace Content.Server.GameTicking
{
/// <summary>
/// Handles some low-level GameTicker behavior such as setting up clients when they connect.
/// Does not contain lobby/round handling mechanisms.
/// </summary>
public abstract class GameTickerBase : SharedGameTicker
{
[Dependency] protected readonly IPlayerManager PlayerManager = default!;
public virtual void Initialize()
{
PlayerManager.PlayerStatusChanged += PlayerStatusChanged;
}
protected virtual void PlayerStatusChanged(object? sender, SessionStatusEventArgs args)
{
var session = args.Session;
if (args.NewStatus == SessionStatus.Connected)
{
// Always make sure the client has player data. Mind gets assigned on spawn.
if (session.Data.ContentDataUncast == null)
session.Data.ContentDataUncast = new PlayerData(session.UserId);
// timer time must be > tick length
Timer.Spawn(0, args.Session.JoinGame);
}
}
}
}

View File

@@ -1,77 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Content.Server.GameTicking.Rules;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Content.Server.GameTicking
{
/// <summary>
/// The game ticker is responsible for managing the round-by-round system of the game.
/// </summary>
public interface IGameTicker
{
GameRunLevel RunLevel { get; }
/// <summary>
/// The map loaded by the GameTicker on round start.
/// </summary>
MapId DefaultMap { get; }
/// <summary>
/// The GridId loaded by the GameTicker on round start.
/// </summary>
GridId DefaultGridId { get; }
event Action<GameRunLevelChangedEventArgs> OnRunLevelChanged;
event Action<GameRuleAddedEventArgs> OnRuleAdded;
void Initialize();
void Update(FrameEventArgs frameEventArgs);
void RestartRound();
void StartRound(bool force = false);
void EndRound(string roundEndText = "");
void Respawn(IPlayerSession targetPlayer);
void MakeObserve(IPlayerSession player);
void MakeJoinGame(IPlayerSession player, string? jobId = null);
void ToggleReady(IPlayerSession player, bool ready);
void ToggleDisallowLateJoin(bool disallowLateJoin);
/// <summary>proxy to GamePreset (actual handler)</summary>
bool OnGhostAttempt(Mind.Mind mind, bool canReturnGlobal);
EntityCoordinates GetLateJoinSpawnPoint();
EntityCoordinates GetJobSpawnPoint(string jobId);
EntityCoordinates GetObserverSpawnPoint();
void EquipStartingGear(IEntity entity, StartingGearPrototype startingGear, HumanoidCharacterProfile? profile);
// GameRule system.
T AddGameRule<T>() where T : GameRule, new();
bool HasGameRule(string? type);
bool HasGameRule(Type? type);
void RemoveGameRule(GameRule rule);
IEnumerable<GameRule> ActiveGameRules { get; }
bool TryGetPreset(string name, [NotNullWhen(true)] 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);
Dictionary<string, int> GetAvailablePositions();
}
}

View File

@@ -45,7 +45,7 @@ namespace Content.Server.GameTicking.Presets
mind.UnVisit(); mind.UnVisit();
} }
var position = playerEntity?.Transform.Coordinates ?? IoCManager.Resolve<IGameTicker>().GetObserverSpawnPoint(); var position = playerEntity?.Transform.Coordinates ?? EntitySystem.Get<GameTicker>().GetObserverSpawnPoint();
// Ok, so, this is the master place for the logic for if ghosting is "too cheaty" to allow returning. // Ok, so, this is the master place for the logic for if ghosting is "too cheaty" to allow returning.
// There's no reason at this time to move it to any other place, especially given that the 'side effects required' situations would also have to be moved. // There's no reason at this time to move it to any other place, especially given that the 'side effects required' situations would also have to be moved.
// + If CharacterDeadPhysically applies, we're physically dead. Therefore, ghosting OK, and we can return (this is critical for gibbing) // + If CharacterDeadPhysically applies, we're physically dead. Therefore, ghosting OK, and we can return (this is critical for gibbing)

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Server.GameTicking.Presets namespace Content.Server.GameTicking.Presets
@@ -8,11 +9,9 @@ namespace Content.Server.GameTicking.Presets
[GamePreset("deathmatch")] [GamePreset("deathmatch")]
public sealed class PresetDeathMatch : GamePreset public sealed class PresetDeathMatch : GamePreset
{ {
[Dependency] private readonly IGameTicker _gameTicker = default!;
public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers, bool force = false) public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers, bool force = false)
{ {
_gameTicker.AddGameRule<RuleDeathMatch>(); EntitySystem.Get<GameTicker>().AddGameRule<RuleDeathMatch>();
return true; return true;
} }

View File

@@ -28,7 +28,6 @@ namespace Content.Server.GameTicking.Presets
public class PresetSuspicion : GamePreset public class PresetSuspicion : GamePreset
{ {
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IGameTicker _gameTicker = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -149,7 +148,7 @@ namespace Content.Server.GameTicking.Presets
traitor.GreetSuspicion(traitors, _chatManager); traitor.GreetSuspicion(traitors, _chatManager);
} }
_gameTicker.AddGameRule<RuleSuspicion>(); EntitySystem.Get<GameTicker>().AddGameRule<RuleSuspicion>();
return true; return true;
} }

View File

@@ -16,6 +16,7 @@ using Content.Shared.Inventory;
using Content.Shared.PDA; using Content.Shared.PDA;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Log; using Robust.Shared.Log;
@@ -29,7 +30,6 @@ namespace Content.Server.GameTicking.Presets
[GamePreset("traitor")] [GamePreset("traitor")]
public class PresetTraitor : GamePreset public class PresetTraitor : GamePreset
{ {
[Dependency] private readonly IGameTicker _gameTicker = default!;
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -159,7 +159,7 @@ namespace Content.Server.GameTicking.Presets
traitor.GreetTraitor(codewords); traitor.GreetTraitor(codewords);
} }
_gameTicker.AddGameRule<RuleTraitor>(); EntitySystem.Get<GameTicker>().AddGameRule<RuleTraitor>();
return true; return true;
} }

View File

@@ -34,7 +34,6 @@ namespace Content.Server.GameTicking.Presets
{ {
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IGameTicker _gameTicker = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!;
@@ -50,8 +49,9 @@ namespace Content.Server.GameTicking.Presets
public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers, bool force = false) public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers, bool force = false)
{ {
_gameTicker.AddGameRule<RuleTraitorDeathMatch>(); var gameTicker = EntitySystem.Get<GameTicker>();
_restarter = _gameTicker.AddGameRule<RuleMaxTimeRestart>(); gameTicker.AddGameRule<RuleTraitorDeathMatch>();
_restarter = gameTicker.AddGameRule<RuleMaxTimeRestart>();
_restarter.RoundMaxTime = TimeSpan.FromMinutes(30); _restarter.RoundMaxTime = TimeSpan.FromMinutes(30);
_restarter.RestartTimer(); _restarter.RestartTimer();
_safeToEndRound = true; _safeToEndRound = true;
@@ -207,7 +207,7 @@ namespace Content.Server.GameTicking.Presets
var session = mind.Session; var session = mind.Session;
if (session == null) if (session == null)
return false; return false;
_gameTicker.Respawn(session); EntitySystem.Get<GameTicker>().Respawn(session);
return true; return true;
} }

View File

@@ -1,9 +1,10 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.GameTicking.Rules namespace Content.Server.GameTicking.Rules
{ {
[PublicAPI] [PublicAPI]
public abstract class GameRule public abstract class GameRule : IEntityEventSubscriber
{ {
public virtual void Added() public virtual void Added()
{ {

View File

@@ -26,7 +26,6 @@ namespace Content.Server.GameTicking.Rules
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IGameTicker _gameTicker = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
private CancellationTokenSource? _checkTimerCancel; private CancellationTokenSource? _checkTimerCancel;
@@ -91,7 +90,7 @@ namespace Content.Server.GameTicking.Rules
_chatManager.DispatchServerAnnouncement(Loc.GetString("Restarting in {0} seconds.", restartDelay)); _chatManager.DispatchServerAnnouncement(Loc.GetString("Restarting in {0} seconds.", restartDelay));
Timer.Spawn(TimeSpan.FromSeconds(restartDelay), () => _gameTicker.RestartRound()); Timer.Spawn(TimeSpan.FromSeconds(restartDelay), () => EntitySystem.Get<GameTicker>().RestartRound());
} }
private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)

View File

@@ -3,6 +3,7 @@ using System;
using System.Threading; using System.Threading;
using Content.Server.Chat.Managers; using Content.Server.Chat.Managers;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Timer = Robust.Shared.Timing.Timer; using Timer = Robust.Shared.Timing.Timer;
@@ -11,9 +12,9 @@ namespace Content.Server.GameTicking.Rules
{ {
public class RuleInactivityTimeRestart : GameRule public class RuleInactivityTimeRestart : GameRule
{ {
[Dependency] private readonly IGameTicker _gameTicker = default!;
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private CancellationTokenSource _timerCancel = new(); private CancellationTokenSource _timerCancel = new();
@@ -24,7 +25,7 @@ namespace Content.Server.GameTicking.Rules
{ {
base.Added(); base.Added();
_gameTicker.OnRunLevelChanged += RunLevelChanged; _entityManager.EventBus.SubscribeEvent<GameRunLevelChangedEvent>(EventSource.Local, this, RunLevelChanged);
_playerManager.PlayerStatusChanged += PlayerStatusChanged; _playerManager.PlayerStatusChanged += PlayerStatusChanged;
} }
@@ -32,7 +33,7 @@ namespace Content.Server.GameTicking.Rules
{ {
base.Removed(); base.Removed();
_gameTicker.OnRunLevelChanged -= RunLevelChanged; _entityManager.EventBus.UnsubscribeEvents(this);
_playerManager.PlayerStatusChanged -= PlayerStatusChanged; _playerManager.PlayerStatusChanged -= PlayerStatusChanged;
StopTimer(); StopTimer();
@@ -52,16 +53,17 @@ namespace Content.Server.GameTicking.Rules
private void TimerFired() private void TimerFired()
{ {
_gameTicker.EndRound(Loc.GetString("Time has run out!")); var gameticker = EntitySystem.Get<GameTicker>();
gameticker.EndRound(Loc.GetString("Time has run out!"));
_chatManager.DispatchServerAnnouncement(Loc.GetString("Restarting in {0} seconds.", (int) RoundEndDelay.TotalSeconds)); _chatManager.DispatchServerAnnouncement(Loc.GetString("Restarting in {0} seconds.", (int) RoundEndDelay.TotalSeconds));
Timer.Spawn(RoundEndDelay, () => _gameTicker.RestartRound()); Timer.Spawn(RoundEndDelay, () => gameticker.RestartRound());
} }
private void RunLevelChanged(GameRunLevelChangedEventArgs args) private void RunLevelChanged(GameRunLevelChangedEvent args)
{ {
switch (args.NewRunLevel) switch (args.New)
{ {
case GameRunLevel.InRound: case GameRunLevel.InRound:
RestartTimer(); RestartTimer();
@@ -75,7 +77,7 @@ namespace Content.Server.GameTicking.Rules
private void PlayerStatusChanged(object? sender, SessionStatusEventArgs e) private void PlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{ {
if (_gameTicker.RunLevel != GameRunLevel.InRound) if (EntitySystem.Get<GameTicker>().RunLevel != GameRunLevel.InRound)
{ {
return; return;
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Threading; using System.Threading;
using Content.Server.Chat.Managers; using Content.Server.Chat.Managers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Timer = Robust.Shared.Timing.Timer; using Timer = Robust.Shared.Timing.Timer;
@@ -9,8 +10,8 @@ namespace Content.Server.GameTicking.Rules
{ {
public sealed class RuleMaxTimeRestart : GameRule public sealed class RuleMaxTimeRestart : GameRule
{ {
[Dependency] private readonly IGameTicker _gameTicker = default!;
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private CancellationTokenSource _timerCancel = new(); private CancellationTokenSource _timerCancel = new();
@@ -21,14 +22,14 @@ namespace Content.Server.GameTicking.Rules
{ {
base.Added(); base.Added();
_gameTicker.OnRunLevelChanged += RunLevelChanged; _entityManager.EventBus.SubscribeEvent<GameRunLevelChangedEvent>(EventSource.Local, this, RunLevelChanged);
} }
public override void Removed() public override void Removed()
{ {
base.Removed(); base.Removed();
_gameTicker.OnRunLevelChanged -= RunLevelChanged; _entityManager.EventBus.UnsubscribeEvents(this);
StopTimer(); StopTimer();
} }
@@ -46,16 +47,16 @@ namespace Content.Server.GameTicking.Rules
private void TimerFired() private void TimerFired()
{ {
_gameTicker.EndRound(Loc.GetString("Time has run out!")); EntitySystem.Get<GameTicker>().EndRound(Loc.GetString("Time has run out!"));
_chatManager.DispatchServerAnnouncement(Loc.GetString("Restarting in {0} seconds.", (int) RoundEndDelay.TotalSeconds)); _chatManager.DispatchServerAnnouncement(Loc.GetString("Restarting in {0} seconds.", (int) RoundEndDelay.TotalSeconds));
Timer.Spawn(RoundEndDelay, () => _gameTicker.RestartRound()); Timer.Spawn(RoundEndDelay, () => EntitySystem.Get<GameTicker>().RestartRound());
} }
private void RunLevelChanged(GameRunLevelChangedEventArgs args) private void RunLevelChanged(GameRunLevelChangedEvent args)
{ {
switch (args.NewRunLevel) switch (args.New)
{ {
case GameRunLevel.InRound: case GameRunLevel.InRound:
RestartTimer(); RestartTimer();

View File

@@ -30,7 +30,6 @@ namespace Content.Server.GameTicking.Rules
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IGameTicker _gameTicker = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
@@ -152,12 +151,13 @@ namespace Content.Server.GameTicking.Rules
break; break;
} }
_gameTicker.EndRound(text); var gameTicker = EntitySystem.Get<GameTicker>();
gameTicker.EndRound(text);
_chatManager.DispatchServerAnnouncement(Loc.GetString("Restarting in {0} seconds.", (int) RoundEndDelay.TotalSeconds)); _chatManager.DispatchServerAnnouncement(Loc.GetString("Restarting in {0} seconds.", (int) RoundEndDelay.TotalSeconds));
_checkTimerCancel.Cancel(); _checkTimerCancel.Cancel();
Timer.Spawn(RoundEndDelay, () => _gameTicker.RestartRound()); Timer.Spawn(RoundEndDelay, () => gameTicker.RestartRound());
} }
} }
} }

View File

@@ -14,7 +14,6 @@ namespace Content.Server.Ghost.Components
public class GhostOnMoveComponent : Component, IRelayMoveInput, IGhostOnMove public class GhostOnMoveComponent : Component, IRelayMoveInput, IGhostOnMove
{ {
public override string Name => "GhostOnMove"; public override string Name => "GhostOnMove";
[Dependency] private readonly IGameTicker _gameTicker = default!;
[DataField("canReturn")] public bool CanReturn { get; set; } = true; [DataField("canReturn")] public bool CanReturn { get; set; } = true;
@@ -24,7 +23,7 @@ namespace Content.Server.Ghost.Components
if (Owner.HasComponent<VisitingMindComponent>()) return; if (Owner.HasComponent<VisitingMindComponent>()) return;
if (!Owner.TryGetComponent(out MindComponent? mind) || !mind.HasMind || mind.Mind!.IsVisitingEntity) return; if (!Owner.TryGetComponent(out MindComponent? mind) || !mind.HasMind || mind.Mind!.IsVisitingEntity) return;
_gameTicker.OnGhostAttempt(mind.Mind!, CanReturn); EntitySystem.Get<GameTicker>().OnGhostAttempt(mind.Mind!, CanReturn);
} }
} }
} }

View File

@@ -4,6 +4,7 @@ using Content.Server.GameTicking;
using Content.Server.Players; using Content.Server.Players;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
namespace Content.Server.Ghost namespace Content.Server.Ghost
@@ -31,7 +32,7 @@ namespace Content.Server.Ghost
return; return;
} }
if (!IoCManager.Resolve<IGameTicker>().OnGhostAttempt(mind, true)) if (!EntitySystem.Get<GameTicker>().OnGhostAttempt(mind, true))
{ {
shell?.WriteLine("You can't ghost right now."); shell?.WriteLine("You can't ghost right now.");
return; return;

View File

@@ -6,6 +6,7 @@ using Content.Server.Holiday.Interfaces;
using Content.Shared; using Content.Shared;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -13,11 +14,11 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.Holiday namespace Content.Server.Holiday
{ {
// ReSharper disable once ClassNeverInstantiated.Global // ReSharper disable once ClassNeverInstantiated.Global
public class HolidayManager : IHolidayManager public class HolidayManager : IHolidayManager, IEntityEventSubscriber
{ {
[Dependency] private readonly IConfigurationManager _configManager = default!; [Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameTicker _gameTicker = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
[ViewVariables] [ViewVariables]
@@ -74,7 +75,7 @@ namespace Content.Server.Holiday
{ {
_configManager.OnValueChanged(CCVars.HolidaysEnabled, OnHolidaysEnableChange, true); _configManager.OnValueChanged(CCVars.HolidaysEnabled, OnHolidaysEnableChange, true);
_gameTicker.OnRunLevelChanged += OnRunLevelChanged; _entityManager.EventBus.SubscribeEvent<GameRunLevelChangedEvent>(EventSource.Local, this, OnRunLevelChanged);
} }
private void OnHolidaysEnableChange(bool enabled) private void OnHolidaysEnableChange(bool enabled)
@@ -84,11 +85,11 @@ namespace Content.Server.Holiday
RefreshCurrentHolidays(); RefreshCurrentHolidays();
} }
private void OnRunLevelChanged(GameRunLevelChangedEventArgs eventArgs) private void OnRunLevelChanged(GameRunLevelChangedEvent eventArgs)
{ {
if (!_enabled) return; if (!_enabled) return;
switch (eventArgs.NewRunLevel) switch (eventArgs.New)
{ {
case GameRunLevel.PreRoundLobby: case GameRunLevel.PreRoundLobby:
RefreshCurrentHolidays(); RefreshCurrentHolidays();

View File

@@ -39,7 +39,6 @@ namespace Content.Server.IoC
{ {
IoCManager.Register<ISharedNotifyManager, ServerNotifyManager>(); IoCManager.Register<ISharedNotifyManager, ServerNotifyManager>();
IoCManager.Register<IServerNotifyManager, ServerNotifyManager>(); IoCManager.Register<IServerNotifyManager, ServerNotifyManager>();
IoCManager.Register<IGameTicker, GameTicker>();
IoCManager.Register<IChatManager, ChatManager>(); IoCManager.Register<IChatManager, ChatManager>();
IoCManager.Register<IMoMMILink, MoMMILink>(); IoCManager.Register<IMoMMILink, MoMMILink>();
IoCManager.Register<ISandboxManager, SandboxManager>(); IoCManager.Register<ISandboxManager, SandboxManager>();

View File

@@ -77,7 +77,7 @@ namespace Content.Server.Mind.Components
base.Shutdown(); base.Shutdown();
// Let's not create ghosts if not in the middle of the round. // Let's not create ghosts if not in the middle of the round.
if (IoCManager.Resolve<IGameTicker>().RunLevel != GameRunLevel.InRound) if (EntitySystem.Get<GameTicker>().RunLevel != GameRunLevel.InRound)
return; return;
if (HasMind) if (HasMind)
@@ -104,7 +104,7 @@ namespace Content.Server.Mind.Components
var gridId = spawnPosition.GetGridId(Owner.EntityManager); var gridId = spawnPosition.GetGridId(Owner.EntityManager);
if (gridId == GridId.Invalid || !mapMan.GridExists(gridId)) if (gridId == GridId.Invalid || !mapMan.GridExists(gridId))
{ {
spawnPosition = IoCManager.Resolve<IGameTicker>().GetObserverSpawnPoint(); spawnPosition = EntitySystem.Get<GameTicker>().GetObserverSpawnPoint();
} }
var ghost = Owner.EntityManager.SpawnEntity("MobObserver", spawnPosition); var ghost = Owner.EntityManager.SpawnEntity("MobObserver", spawnPosition);

View File

@@ -14,7 +14,7 @@ namespace Content.Server.Mind
if (mind == null) return; if (mind == null) return;
IoCManager.Resolve<IGameTicker>().OnGhostAttempt(mind, canReturn); EntitySystem.Get<GameTicker>().OnGhostAttempt(mind, canReturn);
} }
} }
} }

View File

@@ -126,7 +126,7 @@ namespace Content.Server.Morgue.Components
if (mind != null) if (mind != null)
{ {
IoCManager.Resolve<IGameTicker>().OnGhostAttempt(mind, false); EntitySystem.Get<GameTicker>().OnGhostAttempt(mind, false);
mind.OwnedEntity?.PopupMessage(Loc.GetString("You cremate yourself!")); mind.OwnedEntity?.PopupMessage(Loc.GetString("You cremate yourself!"));
} }

View File

@@ -1,14 +0,0 @@
using Content.Shared.GameWindow;
using Robust.Server.Player;
namespace Content.Server.Players
{
public static class PlayerSessionExt
{
public static void RequestWindowAttention(this IPlayerSession session)
{
var msg = session.ConnectedClient.CreateNetMessage<MsgRequestWindowAttention>();
session.ConnectedClient.SendMessage(msg);
}
}
}

View File

@@ -153,7 +153,7 @@ namespace Content.Server.Recycling.Components
if (mind != null) if (mind != null)
{ {
IoCManager.Resolve<IGameTicker>().OnGhostAttempt(mind, false); EntitySystem.Get<GameTicker>().OnGhostAttempt(mind, false);
mind.OwnedEntity?.PopupMessage(Loc.GetString("You recycle yourself!")); mind.OwnedEntity?.PopupMessage(Loc.GetString("You recycle yourself!"));
} }

View File

@@ -15,7 +15,6 @@ namespace Content.Server.RoundEnd
{ {
public class RoundEndSystem : EntitySystem, IResettingEntitySystem public class RoundEndSystem : EntitySystem, IResettingEntitySystem
{ {
[Dependency] private readonly IGameTicker _gameTicker = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
@@ -120,11 +119,12 @@ namespace Content.Server.RoundEnd
private void EndRound() private void EndRound()
{ {
OnRoundEndCountdownFinished?.Invoke(); OnRoundEndCountdownFinished?.Invoke();
_gameTicker.EndRound(); var gameTicker = EntitySystem.Get<GameTicker>();
gameTicker.EndRound();
_chatManager.DispatchServerAnnouncement(Loc.GetString("Restarting the round in {0} seconds...", RestartRoundTime)); _chatManager.DispatchServerAnnouncement(Loc.GetString("Restarting the round in {0} seconds...", RestartRoundTime));
Timer.Spawn(TimeSpan.FromSeconds(RestartRoundTime), () => _gameTicker.RestartRound(), CancellationToken.None); Timer.Spawn(TimeSpan.FromSeconds(RestartRoundTime), () => gameTicker.RestartRound(), CancellationToken.None);
} }
} }
} }

View File

@@ -21,11 +21,10 @@ using static Content.Shared.Inventory.EquipmentSlotDefines;
namespace Content.Server.Sandbox namespace Content.Server.Sandbox
{ {
internal sealed class SandboxManager : SharedSandboxManager, ISandboxManager internal sealed class SandboxManager : SharedSandboxManager, ISandboxManager, IEntityEventSubscriber
{ {
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IServerNetManager _netManager = default!; [Dependency] private readonly IServerNetManager _netManager = default!;
[Dependency] private readonly IGameTicker _gameTicker = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!; [Dependency] private readonly IPlacementManager _placementManager = default!;
[Dependency] private readonly IConGroupController _conGroupController = default!; [Dependency] private readonly IConGroupController _conGroupController = default!;
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!;
@@ -53,7 +52,7 @@ namespace Content.Server.Sandbox
_netManager.RegisterNetMessage<MsgSandboxSuicide>(nameof(MsgSandboxSuicide), SandboxSuicideReceived); _netManager.RegisterNetMessage<MsgSandboxSuicide>(nameof(MsgSandboxSuicide), SandboxSuicideReceived);
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged; _playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
_gameTicker.OnRunLevelChanged += GameTickerOnOnRunLevelChanged; _entityManager.EventBus.SubscribeEvent<GameRunLevelChangedEvent>(EventSource.Local, this, GameTickerOnOnRunLevelChanged);
_placementManager.AllowPlacementFunc = placement => _placementManager.AllowPlacementFunc = placement =>
{ {
@@ -74,10 +73,10 @@ namespace Content.Server.Sandbox
}; };
} }
private void GameTickerOnOnRunLevelChanged(GameRunLevelChangedEventArgs obj) private void GameTickerOnOnRunLevelChanged(GameRunLevelChangedEvent obj)
{ {
// Automatically clear sandbox state when round resets. // Automatically clear sandbox state when round resets.
if (obj.NewRunLevel == GameRunLevel.PreRoundLobby) if (obj.New == GameRunLevel.PreRoundLobby)
{ {
IsSandboxEnabled = false; IsSandboxEnabled = false;
} }
@@ -103,7 +102,7 @@ namespace Content.Server.Sandbox
} }
var player = _playerManager.GetSessionByChannel(message.MsgChannel); var player = _playerManager.GetSessionByChannel(message.MsgChannel);
_gameTicker.Respawn(player); EntitySystem.Get<GameTicker>().Respawn(player);
} }
private void SandboxGiveAccessReceived(MsgSandboxGiveAccess message) private void SandboxGiveAccessReceived(MsgSandboxGiveAccess message)

View File

@@ -1,59 +0,0 @@
using System;
using Content.Server.GameTicking;
using Newtonsoft.Json.Linq;
using Robust.Server;
using Robust.Server.Player;
using Robust.Server.ServerStatus;
using Robust.Shared.IoC;
namespace Content.Server.Shell
{
/// <summary>
/// Tiny helper class to handle status messages. Nothing too complicated.
/// </summary>
public class StatusShell
{
private readonly IPlayerManager _playerManager;
private readonly string _name;
private GameRunLevel _runLevel;
private DateTime _roundStartTime;
public StatusShell()
{
_playerManager = IoCManager.Resolve<IPlayerManager>();
var baseServer = IoCManager.Resolve<IBaseServer>();
var gameTicker = IoCManager.Resolve<IGameTicker>();
gameTicker.OnRunLevelChanged += _runLevelChanged;
_name = baseServer.ServerName;
IoCManager.Resolve<IStatusHost>().OnStatusRequest += _getResponse;
}
private void _getResponse(JObject jObject)
{
lock (this)
{
jObject["name"] = _name;
jObject["players"] = _playerManager.PlayerCount;
jObject["run_level"] = (int) _runLevel;
if (_runLevel >= GameRunLevel.InRound)
{
jObject["round_start_time"] = _roundStartTime.ToString("o");
}
}
}
private void _runLevelChanged(GameRunLevelChangedEventArgs eventArgs)
{
lock (this)
{
_runLevel = eventArgs.NewRunLevel;
if (eventArgs.NewRunLevel == GameRunLevel.InRound)
{
_roundStartTime = DateTime.UtcNow;
}
}
}
}
}

View File

@@ -12,7 +12,6 @@ namespace Content.Server.Spawners.Components
[RegisterComponent] [RegisterComponent]
public class ConditionalSpawnerComponent : Component, IMapInit public class ConditionalSpawnerComponent : Component, IMapInit
{ {
[Dependency] private readonly IGameTicker _gameTicker = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!;
public override string Name => "ConditionalSpawner"; public override string Name => "ConditionalSpawner";
@@ -29,9 +28,9 @@ namespace Content.Server.Spawners.Components
[DataField("chance")] [DataField("chance")]
public float Chance { get; set; } = 1.0f; public float Chance { get; set; } = 1.0f;
private void RuleAdded(GameRuleAddedEventArgs obj) public void RuleAdded(GameRuleAddedEvent obj)
{ {
if(_gameRules.Contains(obj.GameRule.GetType().Name)) if(_gameRules.Contains(obj.Rule.GetType().Name))
Spawn(); Spawn();
} }
@@ -45,7 +44,7 @@ namespace Content.Server.Spawners.Components
foreach (var rule in _gameRules) foreach (var rule in _gameRules)
{ {
if (!_gameTicker.HasGameRule(rule)) continue; if (!EntitySystem.Get<GameTicker>().HasGameRule(rule)) continue;
Spawn(); Spawn();
return; return;
} }
@@ -68,8 +67,6 @@ namespace Content.Server.Spawners.Components
public virtual void MapInit() public virtual void MapInit()
{ {
_gameTicker.OnRuleAdded += RuleAdded;
TrySpawn(); TrySpawn();
} }
} }

View File

@@ -0,0 +1,26 @@
using Content.Server.GameTicking;
using Content.Server.Spawners.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Spawners.EntitySystems
{
[UsedImplicitly]
public class ConditionalSpawnerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GameRuleAddedEvent>(OnRuleAdded);
}
private void OnRuleAdded(GameRuleAddedEvent args)
{
foreach (var spawner in ComponentManager.EntityQuery<ConditionalSpawnerComponent>())
{
spawner.RuleAdded(args);
}
}
}
}

View File

@@ -167,7 +167,7 @@ namespace Content.Server.StationEvents.Events
private bool TryFindRandomTile(out Vector2i tile, IRobustRandom? robustRandom = null) private bool TryFindRandomTile(out Vector2i tile, IRobustRandom? robustRandom = null)
{ {
tile = default; tile = default;
var defaultGridId = IoCManager.Resolve<IGameTicker>().DefaultGridId; var defaultGridId = EntitySystem.Get<GameTicker>().DefaultGridId;
if (!IoCManager.Resolve<IMapManager>().TryGetGrid(defaultGridId, out var grid) || if (!IoCManager.Resolve<IMapManager>().TryGetGrid(defaultGridId, out var grid) ||
!IoCManager.Resolve<IEntityManager>().TryGetEntity(grid.GridEntityId, out _targetGrid)) return false; !IoCManager.Resolve<IEntityManager>().TryGetEntity(grid.GridEntityId, out _targetGrid)) return false;

View File

@@ -66,8 +66,7 @@ namespace Content.Server.StationEvents.Events
if (_timeUntilPulse <= 0.0f) if (_timeUntilPulse <= 0.0f)
{ {
var pauseManager = IoCManager.Resolve<IPauseManager>(); var pauseManager = IoCManager.Resolve<IPauseManager>();
var gameTicker = IoCManager.Resolve<IGameTicker>(); var defaultGrid = IoCManager.Resolve<IMapManager>().GetGrid(EntitySystem.Get<GameTicker>().DefaultGridId);
var defaultGrid = IoCManager.Resolve<IMapManager>().GetGrid(gameTicker.DefaultGridId);
if (pauseManager.IsGridPaused(defaultGrid)) if (pauseManager.IsGridPaused(defaultGrid))
return; return;

View File

@@ -30,7 +30,6 @@ namespace Content.Server.StationEvents
[Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IServerNetManager _netManager = default!; [Dependency] private readonly IServerNetManager _netManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IGameTicker _gameTicker = default!;
[Dependency] private readonly IConGroupController _conGroupController = default!; [Dependency] private readonly IConGroupController _conGroupController = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
@@ -214,7 +213,7 @@ namespace Content.Server.StationEvents
} }
// Stop events from happening in lobby and force active event to end if the round ends // Stop events from happening in lobby and force active event to end if the round ends
if (_gameTicker.RunLevel != GameRunLevel.InRound) if (Get<GameTicker>().RunLevel != GameRunLevel.InRound)
{ {
if (CurrentEvent != null) if (CurrentEvent != null)
{ {

View File

@@ -1,9 +1,11 @@
#nullable enable #nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.GameTicking;
using Content.Shared; using Content.Shared;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Robust.Server.Player; using Robust.Server.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Random; using Robust.Shared.Random;
@@ -46,7 +48,7 @@ namespace Content.Server.Voting.Managers
if (votesYes / (float) total >= ratioRequired) if (votesYes / (float) total >= ratioRequired)
{ {
_chatManager.DispatchServerAnnouncement(Loc.GetString("ui-vote-restart-succeeded")); _chatManager.DispatchServerAnnouncement(Loc.GetString("ui-vote-restart-succeeded"));
_ticker.RestartRound(); EntitySystem.Get<GameTicker>().RestartRound();
} }
else else
{ {
@@ -109,7 +111,7 @@ namespace Content.Server.Voting.Managers
Loc.GetString("ui-vote-gamemode-win", ("winner", Loc.GetString(presets[picked])))); Loc.GetString("ui-vote-gamemode-win", ("winner", Loc.GetString(presets[picked]))));
} }
_ticker.SetStartPreset(picked); EntitySystem.Get<GameTicker>().SetStartPreset(picked);
}; };
} }
} }

View File

@@ -31,7 +31,6 @@ namespace Content.Server.Voting.Managers
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IGameTicker _ticker = default!;
[Dependency] private readonly IAdminManager _adminMgr = default!; [Dependency] private readonly IAdminManager _adminMgr = default!;
private int _nextVoteId = 1; private int _nextVoteId = 1;

View File

@@ -26,12 +26,18 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<bool> public static readonly CVarDef<bool>
EventsEnabled = CVarDef.Create("events.enabled", false, CVar.ARCHIVE | CVar.SERVERONLY); EventsEnabled = CVarDef.Create("events.enabled", false, CVar.ARCHIVE | CVar.SERVERONLY);
public static readonly CVarDef<bool>
GameDummyTicker = CVarDef.Create("game.dummyticker", false, CVar.ARCHIVE | CVar.SERVERONLY);
public static readonly CVarDef<bool> public static readonly CVarDef<bool>
GameLobbyEnabled = CVarDef.Create("game.lobbyenabled", false, CVar.ARCHIVE); GameLobbyEnabled = CVarDef.Create("game.lobbyenabled", false, CVar.ARCHIVE);
public static readonly CVarDef<int> public static readonly CVarDef<int>
GameLobbyDuration = CVarDef.Create("game.lobbyduration", 60, CVar.ARCHIVE); GameLobbyDuration = CVarDef.Create("game.lobbyduration", 60, CVar.ARCHIVE);
public static readonly CVarDef<bool>
GameDisallowLateJoins = CVarDef.Create("game.disallowlatejoins", false, CVar.ARCHIVE | CVar.SERVERONLY);
public static readonly CVarDef<string> public static readonly CVarDef<string>
GameLobbyDefaultPreset = CVarDef.Create("game.defaultpreset", "Suspicion", CVar.ARCHIVE); GameLobbyDefaultPreset = CVarDef.Create("game.defaultpreset", "Suspicion", CVar.ARCHIVE);

View File

@@ -2,278 +2,126 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using Robust.Shared.GameObjects;
using Lidgren.Network;
using Robust.Shared.IoC;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.GameTicking namespace Content.Shared.GameTicking
{ {
public abstract class SharedGameTicker public abstract class SharedGameTicker : EntitySystem
{ {
// See ideally these would be pulled from the job definition or something. // See ideally these would be pulled from the job definition or something.
// But this is easier, and at least it isn't hardcoded. // But this is easier, and at least it isn't hardcoded.
public const string OverflowJob = "Assistant"; public const string OverflowJob = "Assistant";
public const string OverflowJobName = "assistant"; public const string OverflowJobName = "assistant";
}
protected class MsgTickerJoinLobby : NetMessage [Serializable, NetSerializable]
{ public class TickerJoinLobbyEvent : EntityEventArgs
#region REQUIRED
public const MsgGroups GROUP = MsgGroups.Command;
public const string NAME = nameof(MsgTickerJoinLobby);
public MsgTickerJoinLobby(INetChannel channel) : base(NAME, GROUP) { }
#endregion
public override void ReadFromBuffer(NetIncomingMessage buffer)
{ {
} }
public override void WriteToBuffer(NetOutgoingMessage buffer) [Serializable, NetSerializable]
{ public class TickerJoinGameEvent : EntityEventArgs
}
}
protected class MsgTickerJoinGame : NetMessage
{
#region REQUIRED
public const MsgGroups GROUP = MsgGroups.Command;
public const string NAME = nameof(MsgTickerJoinGame);
public MsgTickerJoinGame(INetChannel channel) : base(NAME, GROUP) { }
#endregion
public override void ReadFromBuffer(NetIncomingMessage buffer)
{ {
} }
public override void WriteToBuffer(NetOutgoingMessage buffer) [Serializable, NetSerializable]
public class TickerLateJoinStatusEvent : EntityEventArgs
{ {
} // TODO: Make this a replicated CVar, honestly.
} public bool Disallowed { get; }
protected class MsgTickerLateJoinStatus : NetMessage public TickerLateJoinStatusEvent(bool disallowed)
{ {
#region REQUIRED Disallowed = disallowed;
public const MsgGroups GROUP = MsgGroups.Command;
public const string NAME = nameof(MsgTickerLateJoinStatus);
public bool Disallowed { get; set; }
public MsgTickerLateJoinStatus(INetChannel channel) : base(NAME, GROUP) { }
#endregion
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
Disallowed = buffer.ReadBoolean();
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
buffer.Write(Disallowed);
} }
} }
protected class MsgTickerLobbyStatus : NetMessage [Serializable, NetSerializable]
public class TickerLobbyStatusEvent : EntityEventArgs
{ {
#region REQUIRED public bool IsRoundStarted { get; }
public string? LobbySong { get; }
public const MsgGroups GROUP = MsgGroups.Command; public bool YouAreReady { get; }
public const string NAME = nameof(MsgTickerLobbyStatus);
public MsgTickerLobbyStatus(INetChannel channel) : base(NAME, GROUP) { }
#endregion
public bool IsRoundStarted { get; set; }
public string? LobbySong { get; set; }
public bool YouAreReady { get; set; }
// UTC. // UTC.
public TimeSpan StartTime { get; set; } public TimeSpan StartTime { get; }
public bool Paused { get; set; } public bool Paused { get; }
public override void ReadFromBuffer(NetIncomingMessage buffer) public TickerLobbyStatusEvent(bool isRoundStarted, string? lobbySong, bool youAreReady, TimeSpan startTime, bool paused)
{ {
IsRoundStarted = buffer.ReadBoolean(); IsRoundStarted = isRoundStarted;
LobbySong = buffer.ReadString(); LobbySong = lobbySong;
YouAreReady = youAreReady;
if (IsRoundStarted) StartTime = startTime;
{ Paused = paused;
return;
}
YouAreReady = buffer.ReadBoolean();
StartTime = new TimeSpan(buffer.ReadInt64());
Paused = buffer.ReadBoolean();
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
buffer.Write(IsRoundStarted);
buffer.Write(LobbySong);
if (IsRoundStarted)
{
return;
}
buffer.Write(YouAreReady);
buffer.Write(StartTime.Ticks);
buffer.Write(Paused);
} }
} }
protected class MsgTickerLobbyInfo : NetMessage [Serializable, NetSerializable]
public class TickerLobbyInfoEvent : EntityEventArgs
{ {
#region REQUIRED public string TextBlob { get; }
public const MsgGroups GROUP = MsgGroups.Command; public TickerLobbyInfoEvent(string textBlob)
public const string NAME = nameof(MsgTickerLobbyInfo);
public MsgTickerLobbyInfo(INetChannel channel) : base(NAME, GROUP) { }
#endregion
public string TextBlob { get; set; } = string.Empty;
public override void ReadFromBuffer(NetIncomingMessage buffer)
{ {
TextBlob = buffer.ReadString(); TextBlob = textBlob;
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
buffer.Write(TextBlob);
} }
} }
protected class MsgTickerLobbyCountdown : NetMessage [Serializable, NetSerializable]
public class TickerLobbyCountdownEvent : EntityEventArgs
{ {
#region REQUIRED
public const MsgGroups GROUP = MsgGroups.Command;
public const string NAME = nameof(MsgTickerLobbyCountdown);
public MsgTickerLobbyCountdown(INetChannel channel) : base(NAME, GROUP) { }
#endregion
/// <summary> /// <summary>
/// The game time that the game will start at. /// The game time that the game will start at.
/// </summary> /// </summary>
public TimeSpan StartTime { get; set; } public TimeSpan StartTime { get; }
/// <summary> /// <summary>
/// Whether or not the countdown is paused /// Whether or not the countdown is paused
/// </summary> /// </summary>
public bool Paused { get; set; } public bool Paused { get; }
public override void ReadFromBuffer(NetIncomingMessage buffer) public TickerLobbyCountdownEvent(TimeSpan startTime, bool paused)
{ {
StartTime = new TimeSpan(buffer.ReadInt64()); StartTime = startTime;
Paused = buffer.ReadBoolean(); Paused = paused;
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
buffer.Write(StartTime.Ticks);
buffer.Write(Paused);
} }
} }
protected class MsgTickerLobbyReady : NetMessage [Serializable, NetSerializable]
public class TickerLobbyReadyEvent : EntityEventArgs
{ {
#region REQUIRED
public const MsgGroups GROUP = MsgGroups.Command;
public const string NAME = nameof(MsgTickerLobbyReady);
public MsgTickerLobbyReady(INetChannel channel) : base(NAME, GROUP) { }
#endregion
/// <summary> /// <summary>
/// The Status of the Player in the lobby (ready, observer, ...) /// The Status of the Player in the lobby (ready, observer, ...)
/// </summary> /// </summary>
public Dictionary<NetUserId, PlayerStatus> PlayerStatus { get; set; } = new(); public Dictionary<NetUserId, LobbyPlayerStatus> Status { get; }
public override void ReadFromBuffer(NetIncomingMessage buffer) public TickerLobbyReadyEvent(Dictionary<NetUserId, LobbyPlayerStatus> status)
{ {
PlayerStatus = new Dictionary<NetUserId, PlayerStatus>(); Status = status;
var length = buffer.ReadInt32();
for (int i = 0; i < length; i++)
{
var serializer = IoCManager.Resolve<IRobustSerializer>();
var byteLength = buffer.ReadVariableInt32();
NetUserId userId;
using (var stream = buffer.ReadAlignedMemory(byteLength))
{
serializer.DeserializeDirect(stream, out userId);
}
var status = (PlayerStatus)buffer.ReadByte();
PlayerStatus.Add(userId, status);
} }
} }
public override void WriteToBuffer(NetOutgoingMessage buffer) [Serializable, NetSerializable]
public class TickerJobsAvailableEvent : EntityEventArgs
{ {
var serializer = IoCManager.Resolve<IRobustSerializer>();
buffer.Write(PlayerStatus.Count);
foreach (var p in PlayerStatus)
{
using (var stream = new MemoryStream())
{
serializer.SerializeDirect(stream, p.Key);
buffer.WriteVariableInt32((int) stream.Length);
stream.TryGetBuffer(out var segment);
buffer.Write(segment);
}
buffer.Write((byte)p.Value);
}
}
}
protected class MsgTickerJobsAvailable : NetMessage
{
#region REQUIRED
public const MsgGroups GROUP = MsgGroups.Command;
public const string NAME = nameof(MsgTickerJobsAvailable);
public MsgTickerJobsAvailable(INetChannel channel) : base(NAME, GROUP) { }
#endregion
/// <summary> /// <summary>
/// The Status of the Player in the lobby (ready, observer, ...) /// The Status of the Player in the lobby (ready, observer, ...)
/// </summary> /// </summary>
public string[] JobsAvailable { get; set; } = Array.Empty<string>(); public string[] JobsAvailable { get; }
public override void ReadFromBuffer(NetIncomingMessage buffer) public TickerJobsAvailableEvent(string[] jobsAvailable)
{ {
var amount = buffer.ReadInt32(); JobsAvailable = jobsAvailable;
JobsAvailable = new string[amount]; }
}
for (var i = 0; i < amount; i++) [Serializable, NetSerializable]
public class RoundEndMessageEvent : EntityEventArgs
{ {
JobsAvailable[i] = buffer.ReadString(); [Serializable, NetSerializable]
}
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
buffer.Write(JobsAvailable.Length);
foreach (var job in JobsAvailable)
{
buffer.Write(job);
}
}
}
public struct RoundEndPlayerInfo public struct RoundEndPlayerInfo
{ {
public string PlayerOOCName; public string PlayerOOCName;
@@ -283,81 +131,30 @@ namespace Content.Shared.GameTicking
public bool Observer; public bool Observer;
} }
protected class MsgRoundEndMessage : NetMessage public string GamemodeTitle { get; }
public string RoundEndText { get; }
public TimeSpan RoundDuration { get; }
public int PlayerCount { get; }
public RoundEndPlayerInfo[] AllPlayersEndInfo { get; }
public RoundEndMessageEvent(string gamemodeTitle, string roundEndText, TimeSpan roundDuration, int playerCount,
RoundEndPlayerInfo[] allPlayersEndInfo)
{ {
GamemodeTitle = gamemodeTitle;
#region REQUIRED RoundEndText = roundEndText;
RoundDuration = roundDuration;
public const MsgGroups GROUP = MsgGroups.Command; PlayerCount = playerCount;
public const string NAME = nameof(MsgRoundEndMessage); AllPlayersEndInfo = allPlayersEndInfo;
public MsgRoundEndMessage(INetChannel channel) : base(NAME, GROUP) { }
#endregion
public string GamemodeTitle = string.Empty;
public string RoundEndText = string.Empty;
public TimeSpan RoundDuration;
public int PlayerCount;
public List<RoundEndPlayerInfo> AllPlayersEndInfo = new();
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
GamemodeTitle = buffer.ReadString();
RoundEndText = buffer.ReadString();
var hours = buffer.ReadInt32();
var mins = buffer.ReadInt32();
var seconds = buffer.ReadInt32();
RoundDuration = new TimeSpan(hours, mins, seconds);
PlayerCount = buffer.ReadInt32();
AllPlayersEndInfo = new List<RoundEndPlayerInfo>();
for(var i = 0; i < PlayerCount; i++)
{
var readPlayerData = new RoundEndPlayerInfo
{
PlayerOOCName = buffer.ReadString(),
PlayerICName = buffer.ReadString(),
Role = buffer.ReadString(),
Antag = buffer.ReadBoolean(),
Observer = buffer.ReadBoolean(),
};
AllPlayersEndInfo.Add(readPlayerData);
} }
} }
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
buffer.Write(GamemodeTitle);
buffer.Write(RoundEndText);
buffer.Write(RoundDuration.Hours);
buffer.Write(RoundDuration.Minutes);
buffer.Write(RoundDuration.Seconds);
[Serializable, NetSerializable]
buffer.Write(AllPlayersEndInfo.Count); public enum LobbyPlayerStatus : sbyte
foreach(var playerEndInfo in AllPlayersEndInfo)
{
buffer.Write(playerEndInfo.PlayerOOCName);
buffer.Write(playerEndInfo.PlayerICName);
buffer.Write(playerEndInfo.Role);
buffer.Write(playerEndInfo.Antag);
buffer.Write(playerEndInfo.Observer);
}
}
}
public enum PlayerStatus : byte
{ {
NotReady = 0, NotReady = 0,
Ready, Ready,
Observer, Observer,
} }
} }
}

View File

@@ -1,27 +0,0 @@
#nullable enable
using Lidgren.Network;
using Robust.Shared.Network;
namespace Content.Shared.GameWindow
{
public sealed class MsgRequestWindowAttention : NetMessage
{
#region REQUIRED
public const MsgGroups GROUP = MsgGroups.Command;
public const string NAME = nameof(MsgRequestWindowAttention);
public MsgRequestWindowAttention(INetChannel channel) : base(NAME, GROUP) { }
#endregion
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
// Nothing
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
// Nothing
}
}
}

View File

@@ -0,0 +1,12 @@
#nullable enable
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Shared.GameWindow
{
[Serializable, NetSerializable]
public sealed class RequestWindowAttentionEvent : EntityEventArgs
{
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.IntegrationTests; using Content.IntegrationTests;
using Content.Shared.CCVar;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Markdown.Validation; using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -69,6 +70,7 @@ namespace Content.YAMLLinter
var server = StartServer(new ServerContentIntegrationOption() var server = StartServer(new ServerContentIntegrationOption()
{ {
FailureLogLevel = null, FailureLogLevel = null,
CVarOverrides = { {CCVars.GameDummyTicker.Name, "true"} }
}); });
await server.WaitIdleAsync(); await server.WaitIdleAsync();