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:
committed by
GitHub
parent
15fb554c28
commit
d3a611164b
@@ -2,6 +2,7 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
@@ -15,7 +16,7 @@ namespace Content.Server.GameTicking.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var ticker = IoCManager.Resolve<IGameTicker>();
|
||||
var ticker = EntitySystem.Get<GameTicker>();
|
||||
if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
|
||||
{
|
||||
shell.WriteLine("This can only be executed while the game is in the pre-round lobby.");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
@@ -15,7 +16,7 @@ namespace Content.Server.GameTicking.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var ticker = IoCManager.Resolve<IGameTicker>();
|
||||
var ticker = EntitySystem.Get<GameTicker>();
|
||||
|
||||
if (ticker.RunLevel != GameRunLevel.InRound)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
@@ -14,7 +15,7 @@ namespace Content.Server.GameTicking.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var ticker = IoCManager.Resolve<IGameTicker>();
|
||||
var ticker = EntitySystem.Get<GameTicker>();
|
||||
if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
|
||||
{
|
||||
shell.WriteLine("This can only be executed while the game is in the pre-round lobby.");
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
@@ -21,7 +22,7 @@ namespace Content.Server.GameTicking.Commands
|
||||
Type? preset = null;
|
||||
var presetName = string.Join(" ", args);
|
||||
|
||||
var ticker = IoCManager.Resolve<IGameTicker>();
|
||||
var ticker = EntitySystem.Get<GameTicker>();
|
||||
|
||||
if (args.Length > 0)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Server.Administration;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -30,7 +31,7 @@ namespace Content.Server.GameTicking.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var ticker = IoCManager.Resolve<IGameTicker>();
|
||||
var ticker = EntitySystem.Get<GameTicker>();
|
||||
if (ticker.RunLevel == GameRunLevel.PreRoundLobby)
|
||||
{
|
||||
shell.WriteLine("Round has not started.");
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
@@ -15,7 +16,7 @@ namespace Content.Server.GameTicking.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var ticker = IoCManager.Resolve<IGameTicker>();
|
||||
var ticker = EntitySystem.Get<GameTicker>();
|
||||
ticker.RestartRound();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Administration;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
@@ -20,7 +21,7 @@ namespace Content.Server.GameTicking.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var ticker = IoCManager.Resolve<IGameTicker>();
|
||||
var ticker = EntitySystem.Get<GameTicker>();
|
||||
ticker.MakeObserve(player);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Players;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
@@ -22,7 +23,7 @@ namespace Content.Server.GameTicking.Commands
|
||||
}
|
||||
|
||||
var playerMgr = IoCManager.Resolve<IPlayerManager>();
|
||||
var ticker = IoCManager.Resolve<IGameTicker>();
|
||||
var ticker = EntitySystem.Get<GameTicker>();
|
||||
|
||||
NetUserId userId;
|
||||
if (args.Length == 0)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
@@ -20,7 +21,7 @@ namespace Content.Server.GameTicking.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var ticker = IoCManager.Resolve<IGameTicker>();
|
||||
var ticker = EntitySystem.Get<GameTicker>();
|
||||
|
||||
ticker.SetStartPreset(args[0]);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
@@ -15,7 +16,7 @@ namespace Content.Server.GameTicking.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var ticker = IoCManager.Resolve<IGameTicker>();
|
||||
var ticker = EntitySystem.Get<GameTicker>();
|
||||
|
||||
if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
@@ -20,11 +23,11 @@ namespace Content.Server.GameTicking.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var ticker = IoCManager.Resolve<IGameTicker>();
|
||||
var cfgMan = IoCManager.Resolve<IConfigurationManager>();
|
||||
|
||||
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.");
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Administration;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.GameTicking.Commands
|
||||
@@ -20,7 +21,7 @@ namespace Content.Server.GameTicking.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var ticker = IoCManager.Resolve<IGameTicker>();
|
||||
var ticker = EntitySystem.Get<GameTicker>();
|
||||
ticker.ToggleReady(player, bool.Parse(args[0]));
|
||||
}
|
||||
}
|
||||
|
||||
34
Content.Server/GameTicking/GameTicker.CVars.cs
Normal file
34
Content.Server/GameTicking/GameTicker.CVars.cs
Normal 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(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
98
Content.Server/GameTicking/GameTicker.GamePreset.cs
Normal file
98
Content.Server/GameTicking/GameTicker.GamePreset.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
74
Content.Server/GameTicking/GameTicker.GameRule.cs
Normal file
74
Content.Server/GameTicking/GameTicker.GameRule.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,13 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -17,6 +19,9 @@ namespace Content.Server.GameTicking
|
||||
// This code is responsible for the assigning & picking of jobs.
|
||||
public partial class GameTicker
|
||||
{
|
||||
[ViewVariables]
|
||||
private readonly List<ManifestEntry> _manifest = new();
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<string, int> _spawnedPositions = new();
|
||||
|
||||
@@ -187,7 +192,7 @@ namespace Content.Server.GameTicking
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
private void JobControllerInit()
|
||||
private void InitializeJobController()
|
||||
{
|
||||
// Verify that the overflow role exists and has the correct name.
|
||||
var role = _prototypeManager.Index<JobPrototype>(OverflowJob);
|
||||
@@ -202,10 +207,23 @@ namespace Content.Server.GameTicking
|
||||
_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()
|
||||
{
|
||||
var lobbyPlayers = _playersInLobby.Keys.Select(p => p.ConnectedClient).ToList();
|
||||
_netManager.ServerSendToMany(GetJobsAvailable(), lobbyPlayers);
|
||||
RaiseNetworkEvent(GetJobsAvailable(), Filter.Empty().AddPlayers(_playersInLobby.Keys));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
139
Content.Server/GameTicking/GameTicker.Lobby.cs
Normal file
139
Content.Server/GameTicking/GameTicker.Lobby.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Content.Server/GameTicking/GameTicker.LobbyMusic.cs
Normal file
78
Content.Server/GameTicking/GameTicker.LobbyMusic.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
Content.Server/GameTicking/GameTicker.Player.cs
Normal file
144
Content.Server/GameTicking/GameTicker.Player.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
363
Content.Server/GameTicking/GameTicker.RoundFlow.cs
Normal file
363
Content.Server/GameTicking/GameTicker.RoundFlow.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
296
Content.Server/GameTicking/GameTicker.Spawning.cs
Normal file
296
Content.Server/GameTicking/GameTicker.Spawning.cs
Normal 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
|
||||
}
|
||||
}
|
||||
34
Content.Server/GameTicking/GameTicker.StatusShell.cs
Normal file
34
Content.Server/GameTicking/GameTicker.StatusShell.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Content.Server/GameTicking/GameTicker.Updates.cs
Normal file
63
Content.Server/GameTicking/GameTicker.Updates.cs
Normal 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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ namespace Content.Server.GameTicking.Presets
|
||||
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.
|
||||
// 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)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameTicking.Rules;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.GameTicking.Presets
|
||||
@@ -8,11 +9,9 @@ namespace Content.Server.GameTicking.Presets
|
||||
[GamePreset("deathmatch")]
|
||||
public sealed class PresetDeathMatch : GamePreset
|
||||
{
|
||||
[Dependency] private readonly IGameTicker _gameTicker = default!;
|
||||
|
||||
public override bool Start(IReadOnlyList<IPlayerSession> readyPlayers, bool force = false)
|
||||
{
|
||||
_gameTicker.AddGameRule<RuleDeathMatch>();
|
||||
EntitySystem.Get<GameTicker>().AddGameRule<RuleDeathMatch>();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace Content.Server.GameTicking.Presets
|
||||
public class PresetSuspicion : GamePreset
|
||||
{
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IGameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
@@ -149,7 +148,7 @@ namespace Content.Server.GameTicking.Presets
|
||||
traitor.GreetSuspicion(traitors, _chatManager);
|
||||
}
|
||||
|
||||
_gameTicker.AddGameRule<RuleSuspicion>();
|
||||
EntitySystem.Get<GameTicker>().AddGameRule<RuleSuspicion>();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ using Content.Shared.Inventory;
|
||||
using Content.Shared.PDA;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
@@ -29,7 +30,6 @@ namespace Content.Server.GameTicking.Presets
|
||||
[GamePreset("traitor")]
|
||||
public class PresetTraitor : GamePreset
|
||||
{
|
||||
[Dependency] private readonly IGameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -159,7 +159,7 @@ namespace Content.Server.GameTicking.Presets
|
||||
traitor.GreetTraitor(codewords);
|
||||
}
|
||||
|
||||
_gameTicker.AddGameRule<RuleTraitor>();
|
||||
EntitySystem.Get<GameTicker>().AddGameRule<RuleTraitor>();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ namespace Content.Server.GameTicking.Presets
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IGameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = 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)
|
||||
{
|
||||
_gameTicker.AddGameRule<RuleTraitorDeathMatch>();
|
||||
_restarter = _gameTicker.AddGameRule<RuleMaxTimeRestart>();
|
||||
var gameTicker = EntitySystem.Get<GameTicker>();
|
||||
gameTicker.AddGameRule<RuleTraitorDeathMatch>();
|
||||
_restarter = gameTicker.AddGameRule<RuleMaxTimeRestart>();
|
||||
_restarter.RoundMaxTime = TimeSpan.FromMinutes(30);
|
||||
_restarter.RestartTimer();
|
||||
_safeToEndRound = true;
|
||||
@@ -207,7 +207,7 @@ namespace Content.Server.GameTicking.Presets
|
||||
var session = mind.Session;
|
||||
if (session == null)
|
||||
return false;
|
||||
_gameTicker.Respawn(session);
|
||||
EntitySystem.Get<GameTicker>().Respawn(session);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
[PublicAPI]
|
||||
public abstract class GameRule
|
||||
public abstract class GameRule : IEntityEventSubscriber
|
||||
{
|
||||
public virtual void Added()
|
||||
{
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace Content.Server.GameTicking.Rules
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IGameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private CancellationTokenSource? _checkTimerCancel;
|
||||
@@ -91,7 +90,7 @@ namespace Content.Server.GameTicking.Rules
|
||||
|
||||
_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)
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Threading;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
@@ -11,9 +12,9 @@ namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
public class RuleInactivityTimeRestart : GameRule
|
||||
{
|
||||
[Dependency] private readonly IGameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private CancellationTokenSource _timerCancel = new();
|
||||
|
||||
@@ -24,7 +25,7 @@ namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
base.Added();
|
||||
|
||||
_gameTicker.OnRunLevelChanged += RunLevelChanged;
|
||||
_entityManager.EventBus.SubscribeEvent<GameRunLevelChangedEvent>(EventSource.Local, this, RunLevelChanged);
|
||||
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
||||
}
|
||||
|
||||
@@ -32,7 +33,7 @@ namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
base.Removed();
|
||||
|
||||
_gameTicker.OnRunLevelChanged -= RunLevelChanged;
|
||||
_entityManager.EventBus.UnsubscribeEvents(this);
|
||||
_playerManager.PlayerStatusChanged -= PlayerStatusChanged;
|
||||
|
||||
StopTimer();
|
||||
@@ -52,16 +53,17 @@ namespace Content.Server.GameTicking.Rules
|
||||
|
||||
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));
|
||||
|
||||
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:
|
||||
RestartTimer();
|
||||
@@ -75,7 +77,7 @@ namespace Content.Server.GameTicking.Rules
|
||||
|
||||
private void PlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (_gameTicker.RunLevel != GameRunLevel.InRound)
|
||||
if (EntitySystem.Get<GameTicker>().RunLevel != GameRunLevel.InRound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
@@ -9,8 +10,8 @@ namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
public sealed class RuleMaxTimeRestart : GameRule
|
||||
{
|
||||
[Dependency] private readonly IGameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private CancellationTokenSource _timerCancel = new();
|
||||
|
||||
@@ -21,14 +22,14 @@ namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
base.Added();
|
||||
|
||||
_gameTicker.OnRunLevelChanged += RunLevelChanged;
|
||||
_entityManager.EventBus.SubscribeEvent<GameRunLevelChangedEvent>(EventSource.Local, this, RunLevelChanged);
|
||||
}
|
||||
|
||||
public override void Removed()
|
||||
{
|
||||
base.Removed();
|
||||
|
||||
_gameTicker.OnRunLevelChanged -= RunLevelChanged;
|
||||
_entityManager.EventBus.UnsubscribeEvents(this);
|
||||
StopTimer();
|
||||
}
|
||||
|
||||
@@ -46,16 +47,16 @@ namespace Content.Server.GameTicking.Rules
|
||||
|
||||
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));
|
||||
|
||||
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:
|
||||
RestartTimer();
|
||||
|
||||
@@ -30,7 +30,6 @@ namespace Content.Server.GameTicking.Rules
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IGameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
@@ -152,12 +151,13 @@ namespace Content.Server.GameTicking.Rules
|
||||
break;
|
||||
}
|
||||
|
||||
_gameTicker.EndRound(text);
|
||||
var gameTicker = EntitySystem.Get<GameTicker>();
|
||||
gameTicker.EndRound(text);
|
||||
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("Restarting in {0} seconds.", (int) RoundEndDelay.TotalSeconds));
|
||||
_checkTimerCancel.Cancel();
|
||||
|
||||
Timer.Spawn(RoundEndDelay, () => _gameTicker.RestartRound());
|
||||
Timer.Spawn(RoundEndDelay, () => gameTicker.RestartRound());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user