diff --git a/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs b/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs
index 9878fc0767..3019553615 100644
--- a/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs
+++ b/Content.Client/GameObjects/Components/Storage/ClientStorageComponent.cs
@@ -123,6 +123,11 @@ namespace Content.Client.GameObjects.Components.Storage
if(!spriteComp.Running)
return;
+ if (spriteComp.BaseRSI == null)
+ {
+ return;
+ }
+
var baseName = spriteComp.LayerGetState(0).Name;
var stateId = open ? $"{baseName}_open" : $"{baseName}_door";
diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj
index 1e57e86ca7..6fa3c3aa0f 100644
--- a/Content.Server/Content.Server.csproj
+++ b/Content.Server/Content.Server.csproj
@@ -108,6 +108,9 @@
+
+
+
@@ -116,6 +119,7 @@
+
@@ -171,4 +175,4 @@
-
+
\ No newline at end of file
diff --git a/Content.Server/EntryPoint.cs b/Content.Server/EntryPoint.cs
index 45d9270e29..ad7002a26f 100644
--- a/Content.Server/EntryPoint.cs
+++ b/Content.Server/EntryPoint.cs
@@ -34,24 +34,21 @@ using Content.Server.GameObjects.EntitySystems;
using Content.Server.Mobs;
using Content.Server.Players;
using Content.Server.GameObjects.Components.Interactable;
+using Content.Server.GameTicking;
using Content.Server.Interfaces;
+using Content.Server.Interfaces.GameTicking;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.Interfaces;
+using SS14.Shared.Timing;
namespace Content.Server
{
public class EntryPoint : GameServer
{
- const string PlayerPrototypeName = "HumanMob_Content";
-
private IBaseServer _server;
private IPlayerManager _players;
- private IEntityManager entityManager;
private IChatManager chatManager;
-
- private bool _countdownStarted;
-
- private GridLocalCoordinates SpawnPoint;
+ private IGameTicker _gameTicker;
///
public override void Init()
@@ -60,7 +57,6 @@ namespace Content.Server
_server = IoCManager.Resolve();
_players = IoCManager.Resolve();
- entityManager = IoCManager.Resolve();
chatManager = IoCManager.Resolve();
_players.PlayerStatusChanged += HandlePlayerStatusChanged;
@@ -129,8 +125,11 @@ namespace Content.Server
IoCManager.Register();
IoCManager.Register();
+ IoCManager.Register();
IoCManager.BuildGraph();
+ _gameTicker = IoCManager.Resolve();
+
IoCManager.Resolve().Initialize();
}
@@ -138,18 +137,7 @@ namespace Content.Server
{
base.PostInit();
- var timing = IoCManager.Resolve();
- var mapLoader = IoCManager.Resolve();
- var mapMan = IoCManager.Resolve();
-
- var newMap = mapMan.CreateMap();
- var grid = mapLoader.LoadBlueprint(newMap, "Maps/stationstation.yml");
-
- SpawnPoint = new GridLocalCoordinates(Vector2.Zero, grid);
-
- var startTime = timing.RealTime;
- var timeSpan = timing.RealTime - startTime;
- Logger.Info($"Loaded map in {timeSpan.TotalMilliseconds:N2}ms.");
+ _gameTicker.Initialize();
}
///
@@ -163,6 +151,13 @@ namespace Content.Server
base.Dispose(disposing);
}
+ public override void Update(AssemblyLoader.UpdateLevel level, float frameTime)
+ {
+ base.Update(level, frameTime);
+
+ _gameTicker.Update(new FrameEventArgs(frameTime));
+ }
+
private void HandlePlayerStatusChanged(object sender, SessionStatusEventArgs args)
{
var session = args.Session;
@@ -170,67 +165,60 @@ namespace Content.Server
switch (args.NewStatus)
{
case SessionStatus.Connected:
+ {
+ if (session.Data.ContentDataUncast == null)
{
- if (session.Data.ContentDataUncast == null)
- {
- session.Data.ContentDataUncast = new PlayerData(session.SessionId);
- }
- // timer time must be > tick length
- Timer.Spawn(0, args.Session.JoinLobby);
-
- chatManager.DispatchMessage(ChatChannel.Server, "Gamemode: Player joined server!", args.Session.SessionId);
+ session.Data.ContentDataUncast = new PlayerData(session.SessionId);
}
+
+ // timer time must be > tick length
+ Timer.Spawn(0, args.Session.JoinLobby);
+
+ chatManager.DispatchMessage(ChatChannel.Server, "Gamemode: Player joined server!",
+ args.Session.SessionId);
+ }
break;
case SessionStatus.InLobby:
- {
- chatManager.DispatchMessage(ChatChannel.Server, "Gamemode: Player joined Lobby!", args.Session.SessionId);
- }
+ {
+ chatManager.DispatchMessage(ChatChannel.Server, "Gamemode: Player joined Lobby!",
+ args.Session.SessionId);
+ }
break;
case SessionStatus.InGame:
+ {
+ //TODO: Check for existing mob and re-attach
+ var data = session.ContentData();
+ if (data.Mind == null)
{
- //TODO: Check for existing mob and re-attach
- var data = session.ContentData();
- if (data.Mind == null)
+ // No mind yet (new session), make a new one.
+ data.Mind = new Mind(session.SessionId);
+ var mob = _gameTicker.SpawnPlayerMob();
+ data.Mind.TransferTo(mob);
+ }
+ else
+ {
+ if (data.Mind.CurrentEntity == null)
{
- // No mind yet (new session), make a new one.
- data.Mind = new Mind(session.SessionId);
- var mob = SpawnPlayerMob();
+ var mob = _gameTicker.SpawnPlayerMob();
data.Mind.TransferTo(mob);
}
- else
- {
- if (data.Mind.CurrentEntity == null)
- {
- var mob = SpawnPlayerMob();
- data.Mind.TransferTo(mob);
- }
- session.AttachToEntity(data.Mind.CurrentEntity);
- }
- chatManager.DispatchMessage(ChatChannel.Server, "Gamemode: Player joined Game!", args.Session.SessionId);
+
+ session.AttachToEntity(data.Mind.CurrentEntity);
}
+
+ chatManager.DispatchMessage(ChatChannel.Server, "Gamemode: Player joined Game!",
+ args.Session.SessionId);
+ }
break;
case SessionStatus.Disconnected:
- {
- chatManager.DispatchMessage(ChatChannel.Server, "Gamemode: Player left!", args.Session.SessionId);
- }
+ {
+ chatManager.DispatchMessage(ChatChannel.Server, "Gamemode: Player left!", args.Session.SessionId);
+ }
break;
}
}
-
- IEntity SpawnPlayerMob()
- {
- var entity = entityManager.ForceSpawnEntityAt(PlayerPrototypeName, SpawnPoint);
- var shoes = entityManager.SpawnEntity("ShoesItem");
- var uniform = entityManager.SpawnEntity("UniformAssistant");
- if (entity.TryGetComponent(out InventoryComponent inventory))
- {
- inventory.Equip(EquipmentSlotDefines.Slots.INNERCLOTHING, uniform.GetComponent());
- inventory.Equip(EquipmentSlotDefines.Slots.SHOES, shoes.GetComponent());
- }
- return entity;
- }
}
}
diff --git a/Content.Server/GameTicking/GamePreset.cs b/Content.Server/GameTicking/GamePreset.cs
new file mode 100644
index 0000000000..1da340d5aa
--- /dev/null
+++ b/Content.Server/GameTicking/GamePreset.cs
@@ -0,0 +1,10 @@
+namespace Content.Server.GameTicking
+{
+ ///
+ /// A round-start setup preset, such as which antagonists to spawn.
+ ///
+ public abstract class GamePreset
+ {
+ public abstract void Start();
+ }
+}
diff --git a/Content.Server/GameTicking/GamePresets/PresetTraitor.cs b/Content.Server/GameTicking/GamePresets/PresetTraitor.cs
new file mode 100644
index 0000000000..c78fa8f4b3
--- /dev/null
+++ b/Content.Server/GameTicking/GamePresets/PresetTraitor.cs
@@ -0,0 +1,12 @@
+using SS14.Shared.Log;
+
+namespace Content.Server.GameTicking.GamePresets
+{
+ public class PresetTraitor : GamePreset
+ {
+ public override void Start()
+ {
+ Logger.DebugS("ticker.preset", "Current preset is traitor.");
+ }
+ }
+}
diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs
new file mode 100644
index 0000000000..a4886fb5a7
--- /dev/null
+++ b/Content.Server/GameTicking/GameTicker.cs
@@ -0,0 +1,238 @@
+using System;
+using System.Linq;
+using Content.Server.GameObjects;
+using Content.Server.GameTicking.GamePresets;
+using Content.Server.Interfaces.GameTicking;
+using Content.Shared.GameObjects.Components.Inventory;
+using JetBrains.Annotations;
+using SS14.Server.Interfaces;
+using SS14.Server.Interfaces.Console;
+using SS14.Server.Interfaces.Maps;
+using SS14.Server.Interfaces.Player;
+using SS14.Shared.Configuration;
+using SS14.Shared.Interfaces.Configuration;
+using SS14.Shared.Interfaces.GameObjects;
+using SS14.Shared.Interfaces.Map;
+using SS14.Shared.Interfaces.Timing;
+using SS14.Shared.IoC;
+using SS14.Shared.Log;
+using SS14.Shared.Map;
+using SS14.Shared.Maths;
+using SS14.Shared.Timing;
+using SS14.Shared.Utility;
+using SS14.Shared.ViewVariables;
+
+namespace Content.Server.GameTicking
+{
+ public class GameTicker : IGameTicker
+ {
+ [ViewVariables]
+ public GameRunLevel RunLevel
+ {
+ get => _runLevel;
+ private set
+ {
+ if (_runLevel == value)
+ {
+ return;
+ }
+
+ var old = _runLevel;
+ _runLevel = value;
+
+ OnRunLevelChanged?.Invoke(new GameRunLevelChangedEventArgs(old, value));
+ }
+ }
+
+ private const string PlayerPrototypeName = "HumanMob_Content";
+ private const string MapFile = "Maps/stationstation.yml";
+
+ private bool _initialized;
+ [ViewVariables(VVAccess.ReadWrite)] private GridLocalCoordinates _spawnPoint;
+
+#pragma warning disable 649
+ [Dependency] private IEntityManager _entityManager;
+ [Dependency] private IMapManager _mapManager;
+ [Dependency] private IMapLoader _mapLoader;
+ [Dependency] private IGameTiming _gameTiming;
+ [Dependency] private IConfigurationManager _configurationManager;
+#pragma warning restore 649
+
+ public Action OnRunLevelChanged;
+ private GameRunLevel _runLevel;
+
+ public void Initialize()
+ {
+ DebugTools.Assert(!_initialized);
+
+ _configurationManager.RegisterCVar("game.lobbyenabled", false, CVar.ARCHIVE);
+
+ RestartRound();
+
+ _initialized = true;
+ }
+
+ public void Update(FrameEventArgs frameEventArgs)
+ {
+ }
+
+ public void RestartRound()
+ {
+ Logger.InfoS("ticker", "Restarting round!");
+
+ RunLevel = GameRunLevel.PreRoundLobby;
+ _resettingCleanup();
+ _preRoundSetup();
+
+ if (_configurationManager.GetCVar("game.lobbyenabled"))
+ {
+ throw new NotImplementedException();
+ }
+
+ StartRound();
+ }
+
+ public void StartRound()
+ {
+ DebugTools.Assert(RunLevel == GameRunLevel.PreRoundLobby);
+ Logger.InfoS("ticker", "Starting round!");
+
+ RunLevel = GameRunLevel.InRound;
+
+ // TODO: Allow other presets to be selected.
+ var preset = new PresetTraitor();
+ preset.Start();
+ }
+
+ public void EndRound()
+ {
+ DebugTools.Assert(RunLevel == GameRunLevel.InRound);
+ Logger.InfoS("ticker", "Ending round!");
+
+ RunLevel = GameRunLevel.PostRound;
+ }
+
+ public IEntity SpawnPlayerMob()
+ {
+ var entity = _entityManager.ForceSpawnEntityAt(PlayerPrototypeName, _spawnPoint);
+ var shoes = _entityManager.SpawnEntity("ShoesItem");
+ var uniform = _entityManager.SpawnEntity("UniformAssistant");
+ if (entity.TryGetComponent(out InventoryComponent inventory))
+ {
+ inventory.Equip(EquipmentSlotDefines.Slots.INNERCLOTHING, uniform.GetComponent());
+ inventory.Equip(EquipmentSlotDefines.Slots.SHOES, shoes.GetComponent());
+ }
+
+ return entity;
+ }
+
+ ///
+ /// Cleanup that has to run to clear up anything from the previous round.
+ /// Stuff like wiping the previous map clean.
+ ///
+ private void _resettingCleanup()
+ {
+ // Delete all entities.
+ foreach (var entity in _entityManager.GetEntities().ToList())
+ {
+ // TODO: Maybe something less naive here?
+ // FIXME: Actually, definitely.
+ entity.Delete();
+ }
+
+ // Delete all maps outside of nullspace.
+ foreach (var map in _mapManager.GetAllMaps().ToList())
+ {
+ // TODO: Maybe something less naive here?
+ if (map.Index != MapId.Nullspace)
+ {
+ _mapManager.DeleteMap(map.Index);
+ }
+ }
+ }
+
+ private void _preRoundSetup()
+ {
+ var newMap = _mapManager.CreateMap();
+ var startTime = _gameTiming.RealTime;
+ var grid = _mapLoader.LoadBlueprint(newMap, MapFile);
+
+ _spawnPoint = new GridLocalCoordinates(Vector2.Zero, grid);
+
+ var timeSpan = _gameTiming.RealTime - startTime;
+ Logger.InfoS("ticker", $"Loaded map in {timeSpan.TotalMilliseconds:N2}ms.");
+ }
+ }
+
+ public enum GameRunLevel
+ {
+ PreRoundLobby = 0,
+ InRound,
+ PostRound
+ }
+
+ public class GameRunLevelChangedEventArgs : EventArgs
+ {
+ public GameRunLevel OldRunLevel { get; }
+ public GameRunLevel NewRunLevel { get; }
+
+ public GameRunLevelChangedEventArgs(GameRunLevel oldRunLevel, GameRunLevel newRunLevel)
+ {
+ OldRunLevel = oldRunLevel;
+ NewRunLevel = newRunLevel;
+ }
+ }
+
+ class StartRoundCommand : IClientCommand
+ {
+ public string Command => "startround";
+ public string Description => "Ends PreRoundLobby state and starts the round.";
+ public string Help => String.Empty;
+
+ public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
+ {
+ var ticker = IoCManager.Resolve();
+
+ if (ticker.RunLevel != GameRunLevel.PreRoundLobby)
+ {
+ shell.SendText(player, "This can only be executed while the game is in the pre-round lobby.");
+ return;
+ }
+
+ ticker.StartRound();
+ }
+ }
+
+ class EndRoundCommand : IClientCommand
+ {
+ public string Command => "endround";
+ public string Description => "Ends the round and moves the server to PostRound.";
+ public string Help => String.Empty;
+
+ public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
+ {
+ var ticker = IoCManager.Resolve();
+
+ if (ticker.RunLevel != GameRunLevel.InRound)
+ {
+ shell.SendText(player, "This can only be executed while the game is in a round.");
+ return;
+ }
+
+ ticker.EndRound();
+ }
+ }
+
+ class NewRoundCommand : IClientCommand
+ {
+ public string Command => "restartround";
+ public string Description => "Moves the server from PostRound to a new PreRoundLobby.";
+ public string Help => String.Empty;
+
+ public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
+ {
+ var ticker = IoCManager.Resolve();
+ ticker.RestartRound();
+ }
+ }
+}
diff --git a/Content.Server/Interfaces/GameTicking/IGameTicker.cs b/Content.Server/Interfaces/GameTicking/IGameTicker.cs
new file mode 100644
index 0000000000..6769b963f5
--- /dev/null
+++ b/Content.Server/Interfaces/GameTicking/IGameTicker.cs
@@ -0,0 +1,23 @@
+using Content.Server.GameTicking;
+using SS14.Shared.Interfaces.GameObjects;
+using SS14.Shared.Timing;
+
+namespace Content.Server.Interfaces.GameTicking
+{
+ ///
+ /// The game ticker is responsible for managing the round-by-round system of the game.
+ ///
+ public interface IGameTicker
+ {
+ GameRunLevel RunLevel { get; }
+
+ void Initialize();
+ void Update(FrameEventArgs frameEventArgs);
+
+ void RestartRound();
+ void StartRound();
+ void EndRound();
+
+ IEntity SpawnPlayerMob();
+ }
+}
diff --git a/engine b/engine
index fcc405e6b6..8320339ab0 160000
--- a/engine
+++ b/engine
@@ -1 +1 @@
-Subproject commit fcc405e6b6006cabe193a700491ec8cee848a591
+Subproject commit 8320339ab03d47aeb027a29bacaded56d13cd021