Extremely basic game ticker. (#126)

You can theoretically restart the round (resetting the map) now, but it breaks very badly if a client is connected due to various edge cases/race conditions.
This commit is contained in:
Pieter-Jan Briers
2018-11-22 10:37:58 +01:00
committed by GitHub
parent 744af3be3e
commit 574512f1bf
8 changed files with 345 additions and 65 deletions

View File

@@ -123,6 +123,11 @@ namespace Content.Client.GameObjects.Components.Storage
if(!spriteComp.Running) if(!spriteComp.Running)
return; return;
if (spriteComp.BaseRSI == null)
{
return;
}
var baseName = spriteComp.LayerGetState(0).Name; var baseName = spriteComp.LayerGetState(0).Name;
var stateId = open ? $"{baseName}_open" : $"{baseName}_door"; var stateId = open ? $"{baseName}_open" : $"{baseName}_door";

View File

@@ -108,6 +108,9 @@
<Compile Include="GameObjects\EntitySystems\StorageSystem.cs" /> <Compile Include="GameObjects\EntitySystems\StorageSystem.cs" />
<Compile Include="GameObjects\EntitySystems\WelderSystem.cs" /> <Compile Include="GameObjects\EntitySystems\WelderSystem.cs" />
<Compile Include="GameObjects\EntitySystems\TemperatureSystem.cs" /> <Compile Include="GameObjects\EntitySystems\TemperatureSystem.cs" />
<Compile Include="GameTicking\GamePreset.cs" />
<Compile Include="GameTicking\GamePresets\PresetTraitor.cs" />
<Compile Include="GameTicking\GameTicker.cs" />
<Compile Include="Interfaces\GameObjects\Components\Items\IHandsComponent.cs" /> <Compile Include="Interfaces\GameObjects\Components\Items\IHandsComponent.cs" />
<Compile Include="GameObjects\Components\GUI\ServerHandsComponent.cs" /> <Compile Include="GameObjects\Components\GUI\ServerHandsComponent.cs" />
<Compile Include="GameObjects\Components\GUI\InventoryComponent.cs" /> <Compile Include="GameObjects\Components\GUI\InventoryComponent.cs" />
@@ -116,6 +119,7 @@
<Compile Include="GameObjects\Components\Damage\ResistanceSet.cs" /> <Compile Include="GameObjects\Components\Damage\ResistanceSet.cs" />
<Compile Include="GameObjects\Components\Temperature\TemperatureComponent.cs" /> <Compile Include="GameObjects\Components\Temperature\TemperatureComponent.cs" />
<Compile Include="Interfaces\GameObjects\IOnDamageBehavior.cs" /> <Compile Include="Interfaces\GameObjects\IOnDamageBehavior.cs" />
<Compile Include="Interfaces\GameTicking\IGameTicker.cs" />
<Compile Include="Interfaces\IServerNotifyManager.cs" /> <Compile Include="Interfaces\IServerNotifyManager.cs" />
<Compile Include="Mobs\Commands.cs" /> <Compile Include="Mobs\Commands.cs" />
<Compile Include="Mobs\Mind.cs" /> <Compile Include="Mobs\Mind.cs" />
@@ -171,4 +175,4 @@
<Compile Include="GameObjects\Components\Construction\ConstructorComponent.cs" /> <Compile Include="GameObjects\Components\Construction\ConstructorComponent.cs" />
<Compile Include="GameObjects\Components\Construction\ConstructionComponent.cs" /> <Compile Include="GameObjects\Components\Construction\ConstructionComponent.cs" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -34,24 +34,21 @@ using Content.Server.GameObjects.EntitySystems;
using Content.Server.Mobs; using Content.Server.Mobs;
using Content.Server.Players; using Content.Server.Players;
using Content.Server.GameObjects.Components.Interactable; using Content.Server.GameObjects.Components.Interactable;
using Content.Server.GameTicking;
using Content.Server.Interfaces; using Content.Server.Interfaces;
using Content.Server.Interfaces.GameTicking;
using Content.Shared.GameObjects.Components.Inventory; using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.Interfaces; using Content.Shared.Interfaces;
using SS14.Shared.Timing;
namespace Content.Server namespace Content.Server
{ {
public class EntryPoint : GameServer public class EntryPoint : GameServer
{ {
const string PlayerPrototypeName = "HumanMob_Content";
private IBaseServer _server; private IBaseServer _server;
private IPlayerManager _players; private IPlayerManager _players;
private IEntityManager entityManager;
private IChatManager chatManager; private IChatManager chatManager;
private IGameTicker _gameTicker;
private bool _countdownStarted;
private GridLocalCoordinates SpawnPoint;
/// <inheritdoc /> /// <inheritdoc />
public override void Init() public override void Init()
@@ -60,7 +57,6 @@ namespace Content.Server
_server = IoCManager.Resolve<IBaseServer>(); _server = IoCManager.Resolve<IBaseServer>();
_players = IoCManager.Resolve<IPlayerManager>(); _players = IoCManager.Resolve<IPlayerManager>();
entityManager = IoCManager.Resolve<IEntityManager>();
chatManager = IoCManager.Resolve<IChatManager>(); chatManager = IoCManager.Resolve<IChatManager>();
_players.PlayerStatusChanged += HandlePlayerStatusChanged; _players.PlayerStatusChanged += HandlePlayerStatusChanged;
@@ -129,8 +125,11 @@ namespace Content.Server
IoCManager.Register<ISharedNotifyManager, ServerNotifyManager>(); IoCManager.Register<ISharedNotifyManager, ServerNotifyManager>();
IoCManager.Register<IServerNotifyManager, ServerNotifyManager>(); IoCManager.Register<IServerNotifyManager, ServerNotifyManager>();
IoCManager.Register<IGameTicker, GameTicker>();
IoCManager.BuildGraph(); IoCManager.BuildGraph();
_gameTicker = IoCManager.Resolve<IGameTicker>();
IoCManager.Resolve<IServerNotifyManager>().Initialize(); IoCManager.Resolve<IServerNotifyManager>().Initialize();
} }
@@ -138,18 +137,7 @@ namespace Content.Server
{ {
base.PostInit(); base.PostInit();
var timing = IoCManager.Resolve<IGameTiming>(); _gameTicker.Initialize();
var mapLoader = IoCManager.Resolve<IMapLoader>();
var mapMan = IoCManager.Resolve<IMapManager>();
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.");
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -163,6 +151,13 @@ namespace Content.Server
base.Dispose(disposing); 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) private void HandlePlayerStatusChanged(object sender, SessionStatusEventArgs args)
{ {
var session = args.Session; var session = args.Session;
@@ -170,67 +165,60 @@ namespace Content.Server
switch (args.NewStatus) switch (args.NewStatus)
{ {
case SessionStatus.Connected: case SessionStatus.Connected:
{
if (session.Data.ContentDataUncast == null)
{ {
if (session.Data.ContentDataUncast == null) session.Data.ContentDataUncast = new PlayerData(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);
} }
// timer time must be > tick length
Timer.Spawn(0, args.Session.JoinLobby);
chatManager.DispatchMessage(ChatChannel.Server, "Gamemode: Player joined server!",
args.Session.SessionId);
}
break; break;
case SessionStatus.InLobby: 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; break;
case SessionStatus.InGame: 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 // No mind yet (new session), make a new one.
var data = session.ContentData(); data.Mind = new Mind(session.SessionId);
if (data.Mind == null) var mob = _gameTicker.SpawnPlayerMob();
data.Mind.TransferTo(mob);
}
else
{
if (data.Mind.CurrentEntity == null)
{ {
// No mind yet (new session), make a new one. var mob = _gameTicker.SpawnPlayerMob();
data.Mind = new Mind(session.SessionId);
var mob = SpawnPlayerMob();
data.Mind.TransferTo(mob); data.Mind.TransferTo(mob);
} }
else
{ session.AttachToEntity(data.Mind.CurrentEntity);
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);
} }
chatManager.DispatchMessage(ChatChannel.Server, "Gamemode: Player joined Game!",
args.Session.SessionId);
}
break; break;
case SessionStatus.Disconnected: case SessionStatus.Disconnected:
{ {
chatManager.DispatchMessage(ChatChannel.Server, "Gamemode: Player left!", args.Session.SessionId); chatManager.DispatchMessage(ChatChannel.Server, "Gamemode: Player left!", args.Session.SessionId);
} }
break; 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<ClothingComponent>());
inventory.Equip(EquipmentSlotDefines.Slots.SHOES, shoes.GetComponent<ClothingComponent>());
}
return entity;
}
} }
} }

View File

@@ -0,0 +1,10 @@
namespace Content.Server.GameTicking
{
/// <summary>
/// A round-start setup preset, such as which antagonists to spawn.
/// </summary>
public abstract class GamePreset
{
public abstract void Start();
}
}

View File

@@ -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.");
}
}
}

View File

@@ -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<GameRunLevelChangedEventArgs> 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<bool>("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<ClothingComponent>());
inventory.Equip(EquipmentSlotDefines.Slots.SHOES, shoes.GetComponent<ClothingComponent>());
}
return entity;
}
/// <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()
{
// 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<IGameTicker>();
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<IGameTicker>();
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<IGameTicker>();
ticker.RestartRound();
}
}
}

View File

@@ -0,0 +1,23 @@
using Content.Server.GameTicking;
using SS14.Shared.Interfaces.GameObjects;
using SS14.Shared.Timing;
namespace Content.Server.Interfaces.GameTicking
{
/// <summary>
/// The game ticker is responsible for managing the round-by-round system of the game.
/// </summary>
public interface IGameTicker
{
GameRunLevel RunLevel { get; }
void Initialize();
void Update(FrameEventArgs frameEventArgs);
void RestartRound();
void StartRound();
void EndRound();
IEntity SpawnPlayerMob();
}
}

2
engine

Submodule engine updated: fcc405e6b6...8320339ab0