2019-10-18 14:28:39 +02:00
using System ;
2018-11-25 19:04:49 +01:00
using System.Collections.Generic ;
2018-11-22 10:37:58 +01:00
using System.Linq ;
using Content.Server.GameObjects ;
2019-03-17 15:52:27 +01:00
using Content.Server.GameObjects.Components.Markers ;
2018-11-22 10:37:58 +01:00
using Content.Server.GameTicking.GamePresets ;
2019-12-22 13:47:34 +01:00
using Content.Server.Interfaces ;
2019-04-13 09:45:09 +02:00
using Content.Server.Interfaces.Chat ;
2018-11-22 10:37:58 +01:00
using Content.Server.Interfaces.GameTicking ;
2018-11-25 19:04:49 +01:00
using Content.Server.Mobs ;
2019-11-17 11:18:39 -05:00
using Content.Server.Mobs.Roles ;
2018-11-25 19:04:49 +01:00
using Content.Server.Players ;
using Content.Shared ;
2018-11-22 10:37:58 +01:00
using Content.Shared.GameObjects.Components.Inventory ;
2019-11-17 11:18:39 -05:00
using Content.Shared.Jobs ;
2019-04-15 21:11:38 -06:00
using Robust.Server.Interfaces.Maps ;
using Robust.Server.Interfaces.Player ;
using Robust.Server.Player ;
using Robust.Shared.Configuration ;
using Robust.Shared.Enums ;
using Robust.Shared.GameObjects ;
using Robust.Shared.Interfaces.Configuration ;
using Robust.Shared.Interfaces.GameObjects ;
using Robust.Shared.Interfaces.Map ;
using Robust.Shared.Interfaces.Network ;
2019-08-17 21:09:09 +02:00
using Robust.Shared.Interfaces.Random ;
2019-04-15 21:11:38 -06:00
using Robust.Shared.Interfaces.Timing ;
using Robust.Shared.IoC ;
2019-10-18 14:28:39 +02:00
using Robust.Shared.Localization ;
2019-04-15 21:11:38 -06:00
using Robust.Shared.Log ;
using Robust.Shared.Map ;
using Robust.Shared.Maths ;
2019-11-17 11:18:39 -05:00
using Robust.Shared.Prototypes ;
2019-08-17 21:09:09 +02:00
using Robust.Shared.Random ;
2019-04-15 21:11:38 -06:00
using Robust.Shared.Timers ;
using Robust.Shared.Timing ;
using Robust.Shared.Utility ;
using Robust.Shared.ViewVariables ;
2018-11-22 10:37:58 +01:00
namespace Content.Server.GameTicking
{
2018-11-25 19:04:49 +01:00
public class GameTicker : SharedGameTicker , IGameTicker
2018-11-22 10:37:58 +01:00
{
[ViewVariables]
public GameRunLevel RunLevel
{
get = > _runLevel ;
private set
{
if ( _runLevel = = value )
{
return ;
}
var old = _runLevel ;
_runLevel = value ;
OnRunLevelChanged ? . Invoke ( new GameRunLevelChangedEventArgs ( old , value ) ) ;
}
}
2018-11-26 10:02:47 +01:00
public event Action < GameRunLevelChangedEventArgs > OnRunLevelChanged ;
2018-11-25 19:04:49 +01:00
2018-11-22 10:37:58 +01:00
private const string PlayerPrototypeName = "HumanMob_Content" ;
2018-11-25 19:04:49 +01:00
private const string ObserverPrototypeName = "MobObserver" ;
2018-11-22 10:37:58 +01:00
private const string MapFile = "Maps/stationstation.yml" ;
2018-11-25 19:04:49 +01:00
// Seconds.
private const float LobbyDuration = 20 ;
[ViewVariables] private bool _initialized ;
2019-01-18 11:40:30 +01:00
[ViewVariables(VVAccess.ReadWrite)] private GridCoordinates _spawnPoint ;
2018-11-25 19:04:49 +01:00
[ViewVariables] private GameRunLevel _runLevel ;
[ViewVariables] private bool LobbyEnabled = > _configurationManager . GetCVar < bool > ( "game.lobbyenabled" ) ;
// Value is whether they're ready.
[ViewVariables]
private readonly Dictionary < IPlayerSession , bool > _playersInLobby = new Dictionary < IPlayerSession , bool > ( ) ;
[ViewVariables] private bool _roundStartCountdownHasNotStartedYetDueToNoPlayers ;
private DateTime _roundStartTimeUtc ;
2018-11-22 10:37:58 +01:00
2019-05-08 09:55:36 +02:00
[ViewVariables] private readonly List < GameRule > _gameRules = new List < GameRule > ( ) ;
2019-05-15 15:49:02 +02:00
[ViewVariables] private Type _presetType ;
2018-11-22 10:37:58 +01:00
#pragma warning disable 649
[Dependency] private IEntityManager _entityManager ;
[Dependency] private IMapManager _mapManager ;
[Dependency] private IMapLoader _mapLoader ;
[Dependency] private IGameTiming _gameTiming ;
[Dependency] private IConfigurationManager _configurationManager ;
2018-11-25 19:04:49 +01:00
[Dependency] private IPlayerManager _playerManager ;
[Dependency] private IChatManager _chatManager ;
[Dependency] private IServerNetManager _netManager ;
2019-05-08 09:55:36 +02:00
[Dependency] private IDynamicTypeFactory _dynamicTypeFactory ;
2019-11-17 11:18:39 -05:00
[Dependency] private IPrototypeManager _prototypeManager ;
2019-10-18 14:28:39 +02:00
[Dependency] private readonly ILocalizationManager _localization ;
2019-08-17 21:09:09 +02:00
[Dependency] private readonly IRobustRandom _robustRandom ;
2019-12-22 13:47:34 +01:00
[Dependency] private readonly IServerPreferencesManager _prefsManager ;
2018-11-22 10:37:58 +01:00
#pragma warning restore 649
public void Initialize ( )
{
DebugTools . Assert ( ! _initialized ) ;
_configurationManager . RegisterCVar ( "game.lobbyenabled" , false , CVar . ARCHIVE ) ;
2018-11-25 19:04:49 +01:00
_playerManager . PlayerStatusChanged + = _handlePlayerStatusChanged ;
_netManager . RegisterNetMessage < MsgTickerJoinLobby > ( nameof ( MsgTickerJoinLobby ) ) ;
_netManager . RegisterNetMessage < MsgTickerJoinGame > ( nameof ( MsgTickerJoinGame ) ) ;
_netManager . RegisterNetMessage < MsgTickerLobbyStatus > ( nameof ( MsgTickerLobbyStatus ) ) ;
2019-10-18 14:28:39 +02:00
_netManager . RegisterNetMessage < MsgTickerLobbyInfo > ( nameof ( MsgTickerLobbyInfo ) ) ;
2018-11-22 10:37:58 +01:00
RestartRound ( ) ;
_initialized = true ;
}
public void Update ( FrameEventArgs frameEventArgs )
{
2018-11-25 19:04:49 +01:00
if ( RunLevel ! = GameRunLevel . PreRoundLobby | | _roundStartTimeUtc > DateTime . UtcNow | |
_roundStartCountdownHasNotStartedYetDueToNoPlayers )
{
return ;
}
StartRound ( ) ;
2018-11-22 10:37:58 +01:00
}
public void RestartRound ( )
{
Logger . InfoS ( "ticker" , "Restarting round!" ) ;
RunLevel = GameRunLevel . PreRoundLobby ;
_resettingCleanup ( ) ;
_preRoundSetup ( ) ;
2018-11-25 19:04:49 +01:00
if ( ! LobbyEnabled )
2018-11-22 10:37:58 +01:00
{
2018-11-25 19:04:49 +01:00
StartRound ( ) ;
}
else
{
if ( _playerManager . PlayerCount = = 0 )
{
_roundStartCountdownHasNotStartedYetDueToNoPlayers = true ;
}
else
{
_roundStartTimeUtc = DateTime . UtcNow + TimeSpan . FromSeconds ( LobbyDuration ) ;
}
2019-05-08 16:42:36 +02:00
_sendStatusToAll ( ) ;
2018-11-22 10:37:58 +01:00
}
}
public void StartRound ( )
{
DebugTools . Assert ( RunLevel = = GameRunLevel . PreRoundLobby ) ;
Logger . InfoS ( "ticker" , "Starting round!" ) ;
RunLevel = GameRunLevel . InRound ;
2019-10-18 14:28:39 +02:00
var preset = MakeGamePreset ( ) ;
2018-11-22 10:37:58 +01:00
preset . Start ( ) ;
2018-11-25 19:04:49 +01:00
foreach ( var ( playerSession , ready ) in _playersInLobby . ToList ( ) )
{
2019-05-08 16:42:36 +02:00
if ( LobbyEnabled & & ! ready )
2018-11-25 19:04:49 +01:00
{
continue ;
}
_spawnPlayer ( playerSession ) ;
}
_sendStatusToAll ( ) ;
2018-11-22 10:37:58 +01:00
}
public void EndRound ( )
{
DebugTools . Assert ( RunLevel = = GameRunLevel . InRound ) ;
Logger . InfoS ( "ticker" , "Ending round!" ) ;
RunLevel = GameRunLevel . PostRound ;
}
2018-11-25 19:04:49 +01:00
public void Respawn ( IPlayerSession targetPlayer )
{
targetPlayer . ContentData ( ) . WipeMind ( ) ;
if ( LobbyEnabled )
{
_playerJoinLobby ( targetPlayer ) ;
}
else
{
_spawnPlayer ( targetPlayer ) ;
}
}
public void MakeObserve ( IPlayerSession player )
{
if ( ! _playersInLobby . ContainsKey ( player ) )
{
return ;
}
_spawnObserver ( player ) ;
}
public void MakeJoinGame ( IPlayerSession player )
{
if ( ! _playersInLobby . ContainsKey ( player ) )
{
return ;
}
_spawnPlayer ( player ) ;
}
public void ToggleReady ( IPlayerSession player , bool ready )
{
if ( ! _playersInLobby . ContainsKey ( player ) )
{
return ;
}
_playersInLobby [ player ] = ready ;
_netManager . ServerSendMessage ( _getStatusMsg ( player ) , player . ConnectedClient ) ;
}
2019-05-08 09:55:36 +02:00
public T AddGameRule < T > ( ) where T : GameRule , new ( )
{
var instance = _dynamicTypeFactory . CreateInstance < T > ( ) ;
_gameRules . Add ( instance ) ;
instance . Added ( ) ;
return instance ;
}
public void RemoveGameRule ( GameRule rule )
{
if ( _gameRules . Contains ( rule ) )
{
return ;
}
rule . Removed ( ) ;
_gameRules . Remove ( rule ) ;
}
public IEnumerable < GameRule > ActiveGameRules = > _gameRules ;
2019-05-15 15:49:02 +02:00
public void SetStartPreset ( Type type )
{
if ( ! typeof ( GamePreset ) . IsAssignableFrom ( type ) )
{
throw new ArgumentException ( "type must inherit GamePreset" ) ;
}
_presetType = type ;
2019-10-18 14:28:39 +02:00
UpdateInfoText ( ) ;
2019-05-15 15:49:02 +02:00
}
2019-11-17 11:18:39 -05:00
private IEntity _spawnPlayerMob ( Job job )
2018-11-22 10:37:58 +01:00
{
2019-07-07 22:24:44 +02:00
var entity = _entityManager . SpawnEntityAt ( PlayerPrototypeName , _getLateJoinSpawnPoint ( ) ) ;
2018-11-22 10:37:58 +01:00
if ( entity . TryGetComponent ( out InventoryComponent inventory ) )
{
2019-11-17 11:18:39 -05:00
var gear = _prototypeManager . Index < StartingGearPrototype > ( job . StartingGear ) . Equipment ;
2019-09-09 10:52:45 -07:00
2019-11-17 11:18:39 -05:00
foreach ( var ( slotStr , equipmentStr ) in gear )
{
if ( ! Enum . TryParse ( slotStr . ToUpper ( ) , out EquipmentSlotDefines . Slots slot ) )
{
Logger . Error ( "{0} is an invalid equipment slot." , slotStr ) ;
continue ;
}
2019-12-16 21:44:24 -08:00
var equipmentEntity = _entityManager . SpawnEntity ( equipmentStr , entity . Transform . GridPosition ) ;
2019-11-17 11:18:39 -05:00
inventory . Equip ( slot , equipmentEntity . GetComponent < ClothingComponent > ( ) ) ;
}
2018-11-22 10:37:58 +01:00
}
return entity ;
}
2018-11-25 19:04:49 +01:00
private IEntity _spawnObserverMob ( )
{
2019-07-07 22:24:44 +02:00
return _entityManager . SpawnEntityAt ( ObserverPrototypeName , _getLateJoinSpawnPoint ( ) ) ;
2019-03-17 15:52:27 +01:00
}
private GridCoordinates _getLateJoinSpawnPoint ( )
{
var location = _spawnPoint ;
var possiblePoints = new List < GridCoordinates > ( ) ;
foreach ( var entity in _entityManager . GetEntities ( new TypeEntityQuery ( typeof ( SpawnPointComponent ) ) ) )
{
var point = entity . GetComponent < SpawnPointComponent > ( ) ;
if ( point . SpawnType = = SpawnPointType . LateJoin )
{
possiblePoints . Add ( entity . Transform . GridPosition ) ;
}
}
if ( possiblePoints . Count ! = 0 )
{
2019-08-17 21:09:09 +02:00
location = _robustRandom . Pick ( possiblePoints ) ;
2019-03-17 15:52:27 +01:00
}
return location ;
2018-11-25 19:04:49 +01:00
}
2018-11-22 10:37:58 +01:00
/// <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.
2019-11-26 14:16:36 -08:00
foreach ( var mapId in _mapManager . GetAllMapIds ( ) . ToList ( ) )
2018-11-22 10:37:58 +01:00
{
// TODO: Maybe something less naive here?
2019-11-26 14:16:36 -08:00
if ( mapId ! = MapId . Nullspace )
2018-11-22 10:37:58 +01:00
{
2019-11-26 14:16:36 -08:00
_mapManager . DeleteMap ( mapId ) ;
2018-11-22 10:37:58 +01:00
}
}
2018-11-25 19:04:49 +01:00
// Delete the minds of everybody.
// TODO: Maybe move this into a separate manager?
foreach ( var unCastData in _playerManager . GetAllPlayerData ( ) )
{
unCastData . ContentData ( ) . WipeMind ( ) ;
}
2019-05-08 09:55:36 +02:00
// Clear up any game rules.
foreach ( var rule in _gameRules )
{
rule . Removed ( ) ;
}
_gameRules . Clear ( ) ;
2019-05-08 16:42:36 +02:00
// Move everybody currently in the server to lobby.
foreach ( var player in _playerManager . GetAllPlayers ( ) )
{
if ( _playersInLobby . ContainsKey ( player ) )
{
continue ;
}
_playerJoinLobby ( player ) ;
}
2018-11-22 10:37:58 +01:00
}
private void _preRoundSetup ( )
{
2019-11-26 14:16:36 -08:00
var newMapId = _mapManager . CreateMap ( ) ;
2018-11-22 10:37:58 +01:00
var startTime = _gameTiming . RealTime ;
2019-11-26 14:16:36 -08:00
var grid = _mapLoader . LoadBlueprint ( newMapId , MapFile ) ;
2018-11-22 10:37:58 +01:00
2019-01-18 11:40:30 +01:00
_spawnPoint = new GridCoordinates ( Vector2 . Zero , grid ) ;
2018-11-22 10:37:58 +01:00
var timeSpan = _gameTiming . RealTime - startTime ;
Logger . InfoS ( "ticker" , $"Loaded map in {timeSpan.TotalMilliseconds:N2}ms." ) ;
}
2018-11-25 19:04:49 +01:00
private void _handlePlayerStatusChanged ( object sender , SessionStatusEventArgs args )
{
var session = args . Session ;
switch ( args . NewStatus )
{
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 . SessionId ) ;
}
// timer time must be > tick length
Timer . Spawn ( 0 , args . Session . JoinGame ) ;
2019-04-13 09:45:09 +02:00
_chatManager . DispatchServerAnnouncement ( $"Player {args.Session.SessionId} joined server!" ) ;
2018-11-25 19:04:49 +01:00
if ( LobbyEnabled & & _roundStartCountdownHasNotStartedYetDueToNoPlayers )
{
_roundStartCountdownHasNotStartedYetDueToNoPlayers = false ;
_roundStartTimeUtc = DateTime . UtcNow + TimeSpan . FromSeconds ( LobbyDuration ) ;
}
break ;
}
case SessionStatus . InGame :
{
var data = session . ContentData ( ) ;
if ( data . Mind = = null )
{
if ( LobbyEnabled )
{
_playerJoinLobby ( session ) ;
return ;
}
_spawnPlayer ( session ) ;
}
else
{
if ( data . Mind . CurrentEntity = = null )
{
_spawnPlayer ( session ) ;
}
else
{
session . AttachToEntity ( data . Mind . CurrentEntity ) ;
_playerJoinGame ( session ) ;
}
}
break ;
}
case SessionStatus . Disconnected :
{
if ( _playersInLobby . ContainsKey ( session ) )
{
_playersInLobby . Remove ( session ) ;
}
2019-04-13 09:45:09 +02:00
_chatManager . DispatchServerAnnouncement ( $"Player {args.Session.SessionId} left server!" ) ;
2018-11-25 19:04:49 +01:00
break ;
}
}
}
private void _spawnPlayer ( IPlayerSession session )
{
_playerJoinGame ( session ) ;
var data = session . ContentData ( ) ;
data . WipeMind ( ) ;
data . Mind = new Mind ( session . SessionId ) ;
2019-11-17 11:18:39 -05:00
//TODO Replace "Assistant" with the job when char preference are done
var job = new Job ( data . Mind , _prototypeManager . Index < JobPrototype > ( "Assistant" ) ) ;
data . Mind . AddRole ( job ) ;
2018-11-25 19:04:49 +01:00
2019-11-17 11:18:39 -05:00
var mob = _spawnPlayerMob ( job ) ;
2018-11-25 19:04:49 +01:00
data . Mind . TransferTo ( mob ) ;
}
private void _spawnObserver ( IPlayerSession session )
{
_playerJoinGame ( session ) ;
var data = session . ContentData ( ) ;
data . WipeMind ( ) ;
data . Mind = new Mind ( session . SessionId ) ;
var mob = _spawnObserverMob ( ) ;
data . Mind . TransferTo ( mob ) ;
}
private void _playerJoinLobby ( IPlayerSession session )
{
_playersInLobby . Add ( session , false ) ;
2019-12-22 13:47:34 +01:00
_prefsManager . OnClientConnected ( session ) ;
2018-11-25 19:04:49 +01:00
_netManager . ServerSendMessage ( _netManager . CreateNetMessage < MsgTickerJoinLobby > ( ) , session . ConnectedClient ) ;
_netManager . ServerSendMessage ( _getStatusMsg ( session ) , session . ConnectedClient ) ;
2019-10-18 14:28:39 +02:00
_netManager . ServerSendMessage ( GetInfoMsg ( ) , session . ConnectedClient ) ;
2018-11-25 19:04:49 +01:00
}
private void _playerJoinGame ( IPlayerSession session )
{
2019-07-19 01:21:09 +02:00
_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!" ) ;
2018-11-25 19:04:49 +01:00
if ( _playersInLobby . ContainsKey ( session ) )
{
_playersInLobby . Remove ( session ) ;
}
_netManager . ServerSendMessage ( _netManager . CreateNetMessage < MsgTickerJoinGame > ( ) , session . ConnectedClient ) ;
}
private MsgTickerLobbyStatus _getStatusMsg ( IPlayerSession session )
{
_playersInLobby . TryGetValue ( session , out var ready ) ;
var msg = _netManager . CreateNetMessage < MsgTickerLobbyStatus > ( ) ;
msg . IsRoundStarted = RunLevel ! = GameRunLevel . PreRoundLobby ;
msg . StartTime = _roundStartTimeUtc ;
msg . YouAreReady = ready ;
return msg ;
}
2019-10-18 14:28:39 +02:00
private MsgTickerLobbyInfo GetInfoMsg ( )
{
var msg = _netManager . CreateNetMessage < MsgTickerLobbyInfo > ( ) ;
msg . TextBlob = GetInfoText ( ) ;
return msg ;
}
2018-11-25 19:04:49 +01:00
private void _sendStatusToAll ( )
{
foreach ( var player in _playersInLobby . Keys )
{
_netManager . ServerSendMessage ( _getStatusMsg ( player ) , player . ConnectedClient ) ;
}
}
2019-10-18 14:28:39 +02:00
private string GetInfoText ( )
{
var gameMode = MakeGamePreset ( ) . Description ;
return _localization . GetString ( @ "Hi and welcome to [color=white]Space Station 14![/color]
The current game mode is [ color = white ] { 0 } [ / color ] ", gameMode);
}
private void UpdateInfoText ( )
{
var infoMsg = GetInfoMsg ( ) ;
_netManager . ServerSendToMany ( infoMsg , _playersInLobby . Keys . Select ( p = > p . ConnectedClient ) . ToList ( ) ) ;
}
private GamePreset MakeGamePreset ( )
{
return _dynamicTypeFactory . CreateInstance < GamePreset > ( _presetType ? ? typeof ( PresetSandbox ) ) ;
}
2018-11-22 10:37:58 +01:00
}
public enum GameRunLevel
{
PreRoundLobby = 0 ,
2018-11-26 10:02:47 +01:00
InRound = 1 ,
PostRound = 2
2018-11-22 10:37:58 +01:00
}
public class GameRunLevelChangedEventArgs : EventArgs
{
public GameRunLevel OldRunLevel { get ; }
public GameRunLevel NewRunLevel { get ; }
public GameRunLevelChangedEventArgs ( GameRunLevel oldRunLevel , GameRunLevel newRunLevel )
{
OldRunLevel = oldRunLevel ;
NewRunLevel = newRunLevel ;
}
}
}