diff --git a/Content.Client/GameObjects/Components/Mobs/SpeciesUI.cs b/Content.Client/GameObjects/Components/Mobs/SpeciesUI.cs index be9df9e606..1aecd91c6c 100644 --- a/Content.Client/GameObjects/Components/Mobs/SpeciesUI.cs +++ b/Content.Client/GameObjects/Components/Mobs/SpeciesUI.cs @@ -67,7 +67,6 @@ namespace Content.Client.GameObjects { base.OnAdd(); - IoCManager.InjectDependencies(this); _window = new SpeciesWindow(); EffectsDictionary = new Dictionary() diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj index f7ebce47ec..735f47fc61 100644 --- a/Content.Server/Content.Server.csproj +++ b/Content.Server/Content.Server.csproj @@ -141,7 +141,10 @@ + + + diff --git a/Content.Server/GameObjects/Components/Mobs/DamageStates.cs b/Content.Server/GameObjects/Components/Mobs/DamageStates.cs index 7cbd757679..9c94587403 100644 --- a/Content.Server/GameObjects/Components/Mobs/DamageStates.cs +++ b/Content.Server/GameObjects/Components/Mobs/DamageStates.cs @@ -13,6 +13,8 @@ namespace Content.Server.GameObjects void EnterState(IEntity entity); void ExitState(IEntity entity); + + bool IsConscious { get; } } /// @@ -28,6 +30,8 @@ namespace Content.Server.GameObjects { } + public bool IsConscious => true; + bool IActionBlocker.CanInteract() { return true; @@ -57,6 +61,8 @@ namespace Content.Server.GameObjects { } + public bool IsConscious => false; + bool IActionBlocker.CanInteract() { return false; @@ -96,6 +102,8 @@ namespace Content.Server.GameObjects } } + public bool IsConscious => false; + bool IActionBlocker.CanInteract() { return false; diff --git a/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs b/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs index 74a540fdf5..bb68d25f2f 100644 --- a/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs @@ -112,6 +112,24 @@ namespace Content.Server.GameObjects CurrentDamageState.EnterState(Owner); currentstate = threshold; + + Owner.RaiseEvent(new MobDamageStateChangedMessage(this)); } } + + /// + /// Fired when changes. + /// + public sealed class MobDamageStateChangedMessage : EntitySystemMessage + { + public MobDamageStateChangedMessage(SpeciesComponent species) + { + Species = species; + } + + /// + /// The species component that was changed. + /// + public SpeciesComponent Species { get; } + } } diff --git a/Content.Server/GameTicking/GameRule.cs b/Content.Server/GameTicking/GameRule.cs index 942184b516..6e92818a77 100644 --- a/Content.Server/GameTicking/GameRule.cs +++ b/Content.Server/GameTicking/GameRule.cs @@ -1,7 +1,18 @@ +using JetBrains.Annotations; + namespace Content.Server.GameTicking { - public class GameRule + [PublicAPI] + public abstract class GameRule { - + public virtual void Added() + { + + } + + public virtual void Removed() + { + + } } } diff --git a/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs b/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs index 570af7c63e..cdc9e41d62 100644 --- a/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs +++ b/Content.Server/GameTicking/GameRules/RuleDeathMatch.cs @@ -1,7 +1,112 @@ +using System; +using System.Threading; +using Content.Server.GameObjects; +using Content.Server.Interfaces.Chat; +using Content.Server.Interfaces.GameTicking; +using Robust.Server.Interfaces.Player; +using Robust.Server.Player; +using Robust.Shared.Enums; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.IoC; +using Timer = Robust.Shared.Timers.Timer; + namespace Content.Server.GameTicking.GameRules { - public class DeathMatch + /// + /// Simple GameRule that will do a free-for-all death match. + /// Kill everybody else to win. + /// + public sealed class RuleDeathMatch : GameRule, IEntityEventSubscriber { - + private static readonly TimeSpan DeadCheckDelay = TimeSpan.FromSeconds(5); + +#pragma warning disable 649 + [Dependency] private readonly IPlayerManager _playerManager; + [Dependency] private readonly IEntityManager _entityManager; + [Dependency] private readonly IChatManager _chatManager; + [Dependency] private readonly IGameTicker _gameTicker; +#pragma warning restore 649 + + private CancellationTokenSource _checkTimerCancel; + + public override void Added() + { + _chatManager.DispatchServerAnnouncement("The game is now a death match. Kill everybody else to win!"); + + _entityManager.SubscribeEvent(_onMobDamageStateChanged, this); + _playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged; + } + + public override void Removed() + { + base.Removed(); + + _entityManager.UnsubscribeEvent(this); + _playerManager.PlayerStatusChanged -= PlayerManagerOnPlayerStatusChanged; + } + + private void _onMobDamageStateChanged(object sender, MobDamageStateChangedMessage message) + { + _runDelayedCheck(); + } + + private void _checkForWinner() + { + _checkTimerCancel = null; + + IPlayerSession winner = null; + foreach (var playerSession in _playerManager.GetAllPlayers()) + { + if (playerSession.AttachedEntity == null + || !playerSession.AttachedEntity.TryGetComponent(out SpeciesComponent species)) + { + continue; + } + + if (!species.CurrentDamageState.IsConscious) + { + continue; + } + + if (winner != null) + { + // Found a second person alive, nothing decided yet! + return; + } + + winner = playerSession; + } + + if (winner == null) + { + _chatManager.DispatchServerAnnouncement("Everybody is dead, it's a stalemate!"); + } + else + { + // We have a winner! + _chatManager.DispatchServerAnnouncement($"{winner} wins the death match!"); + } + + _chatManager.DispatchServerAnnouncement($"Restarting in 10 seconds."); + + Timer.Spawn(TimeSpan.FromSeconds(10), () => _gameTicker.RestartRound()); + } + + private void PlayerManagerOnPlayerStatusChanged(object sender, SessionStatusEventArgs e) + { + if (e.NewStatus == SessionStatus.Disconnected) + { + _runDelayedCheck(); + } + } + + private void _runDelayedCheck() + { + _checkTimerCancel?.Cancel(); + _checkTimerCancel = new CancellationTokenSource(); + + Timer.Spawn(DeadCheckDelay, _checkForWinner, _checkTimerCancel.Token); + } } } diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index f9691c16c7..4830150745 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -79,6 +79,8 @@ namespace Content.Server.GameTicking private readonly Random _spawnRandom = new Random(); + [ViewVariables] private readonly List _gameRules = new List(); + #pragma warning disable 649 [Dependency] private IEntityManager _entityManager; [Dependency] private IMapManager _mapManager; @@ -88,6 +90,7 @@ namespace Content.Server.GameTicking [Dependency] private IPlayerManager _playerManager; [Dependency] private IChatManager _chatManager; [Dependency] private IServerNetManager _netManager; + [Dependency] private IDynamicTypeFactory _dynamicTypeFactory; #pragma warning restore 649 public void Initialize() @@ -150,7 +153,7 @@ namespace Content.Server.GameTicking RunLevel = GameRunLevel.InRound; // TODO: Allow other presets to be selected. - var preset = new PresetTraitor(); + var preset = _dynamicTypeFactory.CreateInstance(); preset.Start(); foreach (var (playerSession, ready) in _playersInLobby.ToList()) @@ -219,6 +222,30 @@ namespace Content.Server.GameTicking _netManager.ServerSendMessage(_getStatusMsg(player), player.ConnectedClient); } + public T AddGameRule() where T : GameRule, new() + { + var instance = _dynamicTypeFactory.CreateInstance(); + + _gameRules.Add(instance); + instance.Added(); + + return instance; + } + + public void RemoveGameRule(GameRule rule) + { + if (_gameRules.Contains(rule)) + { + return; + } + + rule.Removed(); + + _gameRules.Remove(rule); + } + + public IEnumerable ActiveGameRules => _gameRules; + private IEntity _spawnPlayerMob() { var entity = _entityManager.ForceSpawnEntityAt(PlayerPrototypeName, _getLateJoinSpawnPoint()); @@ -290,6 +317,14 @@ namespace Content.Server.GameTicking { unCastData.ContentData().WipeMind(); } + + // Clear up any game rules. + foreach (var rule in _gameRules) + { + rule.Removed(); + } + + _gameRules.Clear(); } private void _preRoundSetup() diff --git a/Content.Server/Interfaces/GameTicking/IGameTicker.cs b/Content.Server/Interfaces/GameTicking/IGameTicker.cs index 892d5dec0b..ba737643c4 100644 --- a/Content.Server/Interfaces/GameTicking/IGameTicker.cs +++ b/Content.Server/Interfaces/GameTicking/IGameTicker.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Content.Server.GameTicking; using Robust.Server.Interfaces.Player; using Robust.Server.Player; @@ -27,5 +28,10 @@ namespace Content.Server.Interfaces.GameTicking void MakeObserve(IPlayerSession player); void MakeJoinGame(IPlayerSession player); void ToggleReady(IPlayerSession player, bool ready); + + // GameRule system. + T AddGameRule() where T : GameRule, new(); + void RemoveGameRule(GameRule rule); + IEnumerable ActiveGameRules { get; } } }