2022-01-09 17:54:36 -08:00
using Content.Server.Administration.Logs ;
2021-06-09 22:19:39 +02:00
using Content.Server.EUI ;
using Content.Server.Ghost.Roles.Components ;
2023-04-12 06:32:14 -07:00
using Content.Server.Ghost.Roles.Events ;
2021-06-09 22:19:39 +02:00
using Content.Server.Ghost.Roles.UI ;
2023-08-28 16:53:24 -07:00
using Content.Server.Mind.Commands ;
2021-12-13 16:45:49 +11:00
using Content.Shared.Administration ;
2022-01-09 17:54:36 -08:00
using Content.Shared.Database ;
2022-04-03 01:06:29 +03:00
using Content.Shared.Follower ;
2021-02-12 04:35:56 +01:00
using Content.Shared.GameTicking ;
2021-11-03 23:48:12 +00:00
using Content.Shared.Ghost ;
2021-12-05 21:02:04 +01:00
using Content.Shared.Ghost.Roles ;
2023-08-30 21:46:11 -07:00
using Content.Shared.Mind ;
using Content.Shared.Mind.Components ;
2023-01-13 16:57:10 -08:00
using Content.Shared.Mobs ;
2023-10-28 09:59:53 +11:00
using Content.Shared.Players ;
2023-08-30 21:46:11 -07:00
using Content.Shared.Roles ;
2021-02-12 04:35:56 +01:00
using JetBrains.Annotations ;
using Robust.Server.GameObjects ;
2021-02-12 10:45:22 +01:00
using Robust.Server.Player ;
2021-02-12 04:35:56 +01:00
using Robust.Shared.Console ;
2021-12-05 21:02:04 +01:00
using Robust.Shared.Enums ;
2023-10-28 09:59:53 +11:00
using Robust.Shared.Player ;
2022-07-14 22:20:37 -07:00
using Robust.Shared.Random ;
2021-11-15 18:14:34 +00:00
using Robust.Shared.Utility ;
2021-02-12 04:35:56 +01:00
2021-06-09 22:19:39 +02:00
namespace Content.Server.Ghost.Roles
2021-02-12 04:35:56 +01:00
{
[UsedImplicitly]
2022-02-16 00:23:23 -07:00
public sealed class GhostRoleSystem : EntitySystem
2021-02-12 04:35:56 +01:00
{
[Dependency] private readonly EuiManager _euiManager = default ! ;
2021-11-22 23:11:48 -08:00
[Dependency] private readonly IPlayerManager _playerManager = default ! ;
2022-05-28 23:41:17 -07:00
[Dependency] private readonly IAdminLogManager _adminLogger = default ! ;
2022-07-14 22:20:37 -07:00
[Dependency] private readonly IRobustRandom _random = default ! ;
2022-04-03 01:06:29 +03:00
[Dependency] private readonly FollowerSystem _followerSystem = default ! ;
2023-04-12 06:32:14 -07:00
[Dependency] private readonly TransformSystem _transform = default ! ;
2023-08-30 21:46:11 -07:00
[Dependency] private readonly SharedMindSystem _mindSystem = default ! ;
[Dependency] private readonly SharedRoleSystem _roleSystem = default ! ;
2021-02-12 04:35:56 +01:00
2021-11-22 23:11:48 -08:00
private uint _nextRoleIdentifier ;
2021-11-03 23:48:12 +00:00
private bool _needsUpdateGhostRoleCount = true ;
2023-10-19 12:34:31 -07:00
private readonly Dictionary < uint , Entity < GhostRoleComponent > > _ghostRoles = new ( ) ;
2023-08-30 21:46:11 -07:00
private readonly Dictionary < ICommonSession , GhostRolesEui > _openUis = new ( ) ;
private readonly Dictionary < ICommonSession , MakeGhostRoleEui > _openMakeGhostRoleUis = new ( ) ;
2021-02-12 04:35:56 +01:00
[ViewVariables]
2023-10-19 12:34:31 -07:00
public IReadOnlyCollection < Entity < GhostRoleComponent > > GhostRoles = > _ghostRoles . Values ;
2021-02-12 04:35:56 +01:00
public override void Initialize ( )
{
2021-04-09 16:08:12 +02:00
base . Initialize ( ) ;
2021-06-29 15:56:07 +02:00
SubscribeLocalEvent < RoundRestartCleanupEvent > ( Reset ) ;
2021-05-30 11:39:42 +02:00
SubscribeLocalEvent < PlayerAttachedEvent > ( OnPlayerAttached ) ;
2022-01-12 18:25:05 +11:00
SubscribeLocalEvent < GhostTakeoverAvailableComponent , MindAddedMessage > ( OnMindAdded ) ;
2022-01-01 22:00:21 -08:00
SubscribeLocalEvent < GhostTakeoverAvailableComponent , MindRemovedMessage > ( OnMindRemoved ) ;
SubscribeLocalEvent < GhostTakeoverAvailableComponent , MobStateChangedEvent > ( OnMobStateChanged ) ;
2024-03-27 17:31:26 +13:00
SubscribeLocalEvent < GhostRoleComponent , MapInitEvent > ( OnMapInit ) ;
SubscribeLocalEvent < GhostRoleComponent , ComponentStartup > ( OnStartup ) ;
2022-05-12 06:11:50 -05:00
SubscribeLocalEvent < GhostRoleComponent , ComponentShutdown > ( OnShutdown ) ;
2023-06-10 22:24:34 +10:00
SubscribeLocalEvent < GhostRoleComponent , EntityPausedEvent > ( OnPaused ) ;
SubscribeLocalEvent < GhostRoleComponent , EntityUnpausedEvent > ( OnUnpaused ) ;
2023-04-12 06:32:14 -07:00
SubscribeLocalEvent < GhostRoleMobSpawnerComponent , TakeGhostRoleEvent > ( OnSpawnerTakeRole ) ;
SubscribeLocalEvent < GhostTakeoverAvailableComponent , TakeGhostRoleEvent > ( OnTakeoverTakeRole ) ;
2021-11-03 23:48:12 +00:00
_playerManager . PlayerStatusChanged + = PlayerStatusChanged ;
}
2023-10-19 12:34:31 -07:00
private void OnMobStateChanged ( Entity < GhostTakeoverAvailableComponent > component , ref MobStateChangedEvent args )
2022-01-01 22:00:21 -08:00
{
2023-10-19 12:34:31 -07:00
if ( ! TryComp ( component , out GhostRoleComponent ? ghostRole ) )
2023-04-12 06:32:14 -07:00
return ;
2023-01-13 16:57:10 -08:00
switch ( args . NewMobState )
2022-01-01 22:00:21 -08:00
{
2023-01-13 16:57:10 -08:00
case MobState . Alive :
2022-01-01 22:00:21 -08:00
{
2023-04-12 06:32:14 -07:00
if ( ! ghostRole . Taken )
2023-10-19 12:34:31 -07:00
RegisterGhostRole ( ( component , ghostRole ) ) ;
2022-01-01 22:00:21 -08:00
break ;
}
2023-01-13 16:57:10 -08:00
case MobState . Critical :
case MobState . Dead :
2023-10-19 12:34:31 -07:00
UnregisterGhostRole ( ( component , ghostRole ) ) ;
2022-01-01 22:00:21 -08:00
break ;
}
}
2021-11-03 23:48:12 +00:00
public override void Shutdown ( )
{
base . Shutdown ( ) ;
_playerManager . PlayerStatusChanged - = PlayerStatusChanged ;
2021-02-12 04:35:56 +01:00
}
private uint GetNextRoleIdentifier ( )
{
return unchecked ( _nextRoleIdentifier + + ) ;
}
2023-10-28 09:59:53 +11:00
public void OpenEui ( ICommonSession session )
2021-02-12 04:35:56 +01:00
{
2021-12-05 21:02:04 +01:00
if ( session . AttachedEntity is not { Valid : true } attached | |
! EntityManager . HasComponent < GhostComponent > ( attached ) )
2021-02-12 04:35:56 +01:00
return ;
if ( _openUis . ContainsKey ( session ) )
CloseEui ( session ) ;
var eui = _openUis [ session ] = new GhostRolesEui ( ) ;
_euiManager . OpenEui ( eui , session ) ;
eui . StateDirty ( ) ;
}
2023-10-28 09:59:53 +11:00
public void OpenMakeGhostRoleEui ( ICommonSession session , EntityUid uid )
2021-02-16 09:51:27 +01:00
{
if ( session . AttachedEntity = = null )
return ;
if ( _openMakeGhostRoleUis . ContainsKey ( session ) )
CloseEui ( session ) ;
2023-09-11 14:31:45 +10:00
var eui = _openMakeGhostRoleUis [ session ] = new MakeGhostRoleEui ( EntityManager , GetNetEntity ( uid ) ) ;
2021-02-16 09:51:27 +01:00
_euiManager . OpenEui ( eui , session ) ;
eui . StateDirty ( ) ;
}
2023-08-30 21:46:11 -07:00
public void CloseEui ( ICommonSession session )
2021-02-12 04:35:56 +01:00
{
2023-10-19 12:34:31 -07:00
if ( ! _openUis . ContainsKey ( session ) )
return ;
2021-02-12 04:35:56 +01:00
_openUis . Remove ( session , out var eui ) ;
eui ? . Close ( ) ;
}
2023-08-30 21:46:11 -07:00
public void CloseMakeGhostRoleEui ( ICommonSession session )
2021-02-16 09:51:27 +01:00
{
if ( _openMakeGhostRoleUis . Remove ( session , out var eui ) )
{
2021-11-22 23:11:48 -08:00
eui . Close ( ) ;
2021-02-16 09:51:27 +01:00
}
}
2021-02-12 04:35:56 +01:00
public void UpdateAllEui ( )
{
foreach ( var eui in _openUis . Values )
{
eui . StateDirty ( ) ;
}
2021-11-03 23:48:12 +00:00
// Note that this, like the EUIs, is deferred.
// This is for roughly the same reasons, too:
// Someone might spawn a ton of ghost roles at once.
_needsUpdateGhostRoleCount = true ;
}
public override void Update ( float frameTime )
{
base . Update ( frameTime ) ;
if ( _needsUpdateGhostRoleCount )
{
_needsUpdateGhostRoleCount = false ;
2023-06-10 22:24:34 +10:00
var response = new GhostUpdateGhostRoleCountEvent ( GetGhostRolesInfo ( ) . Length ) ;
2021-11-22 23:11:48 -08:00
foreach ( var player in _playerManager . Sessions )
{
2024-01-22 23:14:13 +01:00
RaiseNetworkEvent ( response , player . Channel ) ;
2021-11-22 23:11:48 -08:00
}
2021-11-03 23:48:12 +00:00
}
}
private void PlayerStatusChanged ( object? blah , SessionStatusEventArgs args )
{
if ( args . NewStatus = = SessionStatus . InGame )
{
var response = new GhostUpdateGhostRoleCountEvent ( _ghostRoles . Count ) ;
2024-01-22 23:14:13 +01:00
RaiseNetworkEvent ( response , args . Session . Channel ) ;
2021-11-03 23:48:12 +00:00
}
2021-02-12 04:35:56 +01:00
}
2023-10-19 12:34:31 -07:00
public void RegisterGhostRole ( Entity < GhostRoleComponent > role )
2021-02-12 04:35:56 +01:00
{
2023-10-19 12:34:31 -07:00
if ( _ghostRoles . ContainsValue ( role ) )
return ;
2021-02-12 04:35:56 +01:00
2023-10-19 12:34:31 -07:00
_ghostRoles [ role . Comp . Identifier = GetNextRoleIdentifier ( ) ] = role ;
UpdateAllEui ( ) ;
2021-02-12 04:35:56 +01:00
}
2023-10-19 12:34:31 -07:00
public void UnregisterGhostRole ( Entity < GhostRoleComponent > role )
2021-02-12 04:35:56 +01:00
{
2023-10-19 12:34:31 -07:00
var comp = role . Comp ;
if ( ! _ghostRoles . ContainsKey ( comp . Identifier ) | | _ghostRoles [ comp . Identifier ] ! = role )
return ;
_ghostRoles . Remove ( comp . Identifier ) ;
2021-02-12 04:35:56 +01:00
UpdateAllEui ( ) ;
}
2023-08-30 21:46:11 -07:00
public void Takeover ( ICommonSession player , uint identifier )
2021-02-12 04:35:56 +01:00
{
2023-10-19 12:34:31 -07:00
if ( ! _ghostRoles . TryGetValue ( identifier , out var role ) )
return ;
2023-04-12 06:32:14 -07:00
var ev = new TakeGhostRoleEvent ( player ) ;
2023-10-19 12:34:31 -07:00
RaiseLocalEvent ( role , ref ev ) ;
2023-04-12 06:32:14 -07:00
2023-10-19 12:34:31 -07:00
if ( ! ev . TookRole )
return ;
2022-01-09 17:54:36 -08:00
if ( player . AttachedEntity ! = null )
2023-10-19 12:34:31 -07:00
_adminLogger . Add ( LogType . GhostRoleTaken , LogImpact . Low , $"{player:player} took the {role.Comp.RoleName:roleName} ghost role {ToPrettyString(player.AttachedEntity.Value):entity}" ) ;
2022-01-09 17:54:36 -08:00
2021-02-12 04:35:56 +01:00
CloseEui ( player ) ;
}
2023-08-30 21:46:11 -07:00
public void Follow ( ICommonSession player , uint identifier )
2022-04-03 01:06:29 +03:00
{
2023-10-19 12:34:31 -07:00
if ( ! _ghostRoles . TryGetValue ( identifier , out var role ) )
return ;
if ( player . AttachedEntity = = null )
return ;
2022-04-03 01:06:29 +03:00
2023-10-19 12:34:31 -07:00
_followerSystem . StartFollowingEntity ( player . AttachedEntity . Value , role ) ;
2022-04-03 01:06:29 +03:00
}
2023-08-30 21:46:11 -07:00
public void GhostRoleInternalCreateMindAndTransfer ( ICommonSession player , EntityUid roleUid , EntityUid mob , GhostRoleComponent ? role = null )
2021-11-15 18:14:34 +00:00
{
2023-10-19 12:34:31 -07:00
if ( ! Resolve ( roleUid , ref role ) )
return ;
2021-11-15 18:14:34 +00:00
2023-06-18 11:33:19 -07:00
DebugTools . AssertNotNull ( player . ContentData ( ) ) ;
2021-11-15 18:14:34 +00:00
2023-06-18 11:33:19 -07:00
var newMind = _mindSystem . CreateMind ( player . UserId ,
EntityManager . GetComponent < MetaDataComponent > ( mob ) . EntityName ) ;
2023-08-28 16:53:24 -07:00
_roleSystem . MindAddRole ( newMind , new GhostRoleMarkerRoleComponent { Name = role . RoleName } ) ;
2021-11-15 18:14:34 +00:00
2023-06-20 16:29:26 +12:00
_mindSystem . SetUserId ( newMind , player . UserId ) ;
2023-06-18 11:33:19 -07:00
_mindSystem . TransferTo ( newMind , mob ) ;
2021-11-15 18:14:34 +00:00
}
2021-02-12 04:35:56 +01:00
public GhostRoleInfo [ ] GetGhostRolesInfo ( )
{
2023-06-10 22:24:34 +10:00
var roles = new List < GhostRoleInfo > ( ) ;
var metaQuery = GetEntityQuery < MetaDataComponent > ( ) ;
2021-02-12 04:35:56 +01:00
2023-10-19 12:34:31 -07:00
foreach ( var ( id , ( uid , role ) ) in _ghostRoles )
2021-02-12 04:35:56 +01:00
{
2023-06-10 22:24:34 +10:00
if ( metaQuery . GetComponent ( uid ) . EntityPaused )
continue ;
2023-09-20 08:54:53 +01:00
roles . Add ( new GhostRoleInfo { Identifier = id , Name = role . RoleName , Description = role . RoleDescription , Rules = role . RoleRules , Requirements = role . Requirements } ) ;
2021-02-12 04:35:56 +01:00
}
2023-06-10 22:24:34 +10:00
return roles . ToArray ( ) ;
2021-02-12 04:35:56 +01:00
}
2021-05-30 11:39:42 +02:00
private void OnPlayerAttached ( PlayerAttachedEvent message )
2021-02-12 04:35:56 +01:00
{
// Close the session of any player that has a ghost roles window open and isn't a ghost anymore.
2023-10-19 12:34:31 -07:00
if ( ! _openUis . ContainsKey ( message . Player ) )
return ;
if ( HasComp < GhostComponent > ( message . Entity ) )
return ;
2021-05-30 11:39:42 +02:00
CloseEui ( message . Player ) ;
2021-02-12 04:35:56 +01:00
}
2022-01-12 18:25:05 +11:00
private void OnMindAdded ( EntityUid uid , GhostTakeoverAvailableComponent component , MindAddedMessage args )
{
2023-04-12 06:32:14 -07:00
if ( ! TryComp ( uid , out GhostRoleComponent ? ghostRole ) )
return ;
ghostRole . Taken = true ;
2023-10-19 12:34:31 -07:00
UnregisterGhostRole ( ( uid , ghostRole ) ) ;
2022-01-12 18:25:05 +11:00
}
2023-04-12 06:32:14 -07:00
private void OnMindRemoved ( EntityUid uid , GhostTakeoverAvailableComponent component , MindRemovedMessage args )
2022-01-01 08:48:45 -08:00
{
2023-04-12 06:32:14 -07:00
if ( ! TryComp ( uid , out GhostRoleComponent ? ghostRole ) )
return ;
2022-01-11 14:12:19 +11:00
// Avoid re-registering it for duplicate entries and potential exceptions.
2023-04-12 06:32:14 -07:00
if ( ! ghostRole . ReregisterOnGhost | | component . LifeStage > ComponentLifeStage . Running )
2022-01-01 08:48:45 -08:00
return ;
2022-01-11 14:12:19 +11:00
2023-04-12 06:32:14 -07:00
ghostRole . Taken = false ;
2023-10-19 12:34:31 -07:00
RegisterGhostRole ( ( uid , ghostRole ) ) ;
2022-01-01 08:48:45 -08:00
}
2021-06-29 15:56:07 +02:00
public void Reset ( RoundRestartCleanupEvent ev )
2021-02-12 04:35:56 +01:00
{
foreach ( var session in _openUis . Keys )
{
CloseEui ( session ) ;
}
_openUis . Clear ( ) ;
_ghostRoles . Clear ( ) ;
_nextRoleIdentifier = 0 ;
}
2022-05-12 06:11:50 -05:00
2023-06-10 22:24:34 +10:00
private void OnPaused ( EntityUid uid , GhostRoleComponent component , ref EntityPausedEvent args )
{
if ( HasComp < ActorComponent > ( uid ) )
return ;
UpdateAllEui ( ) ;
}
private void OnUnpaused ( EntityUid uid , GhostRoleComponent component , ref EntityUnpausedEvent args )
{
if ( HasComp < ActorComponent > ( uid ) )
return ;
UpdateAllEui ( ) ;
}
2024-03-27 17:31:26 +13:00
private void OnMapInit ( Entity < GhostRoleComponent > ent , ref MapInitEvent args )
2022-05-12 06:11:50 -05:00
{
2024-03-27 17:31:26 +13:00
if ( ent . Comp . Probability < 1f & & ! _random . Prob ( ent . Comp . Probability ) )
RemCompDeferred < GhostRoleComponent > ( ent ) ;
}
2022-07-14 22:20:37 -07:00
2024-03-27 17:31:26 +13:00
private void OnStartup ( Entity < GhostRoleComponent > ent , ref ComponentStartup args )
{
2023-10-19 12:34:31 -07:00
RegisterGhostRole ( ent ) ;
2022-05-12 06:11:50 -05:00
}
2023-10-19 12:34:31 -07:00
private void OnShutdown ( Entity < GhostRoleComponent > role , ref ComponentShutdown args )
2022-05-12 06:11:50 -05:00
{
UnregisterGhostRole ( role ) ;
}
2023-04-12 06:32:14 -07:00
private void OnSpawnerTakeRole ( EntityUid uid , GhostRoleMobSpawnerComponent component , ref TakeGhostRoleEvent args )
{
if ( ! TryComp ( uid , out GhostRoleComponent ? ghostRole ) | |
2023-06-10 22:24:34 +10:00
! CanTakeGhost ( uid , ghostRole ) )
2023-04-12 06:32:14 -07:00
{
args . TookRole = false ;
return ;
}
if ( string . IsNullOrEmpty ( component . Prototype ) )
throw new NullReferenceException ( "Prototype string cannot be null or empty!" ) ;
var mob = Spawn ( component . Prototype , Transform ( uid ) . Coordinates ) ;
_transform . AttachToGridOrMap ( mob ) ;
var spawnedEvent = new GhostRoleSpawnerUsedEvent ( uid , mob ) ;
RaiseLocalEvent ( mob , spawnedEvent ) ;
if ( ghostRole . MakeSentient )
MakeSentientCommand . MakeSentient ( mob , EntityManager , ghostRole . AllowMovement , ghostRole . AllowSpeech ) ;
2023-10-11 02:19:46 -07:00
EnsureComp < MindContainerComponent > ( mob ) ;
2023-04-12 06:32:14 -07:00
GhostRoleInternalCreateMindAndTransfer ( args . Player , uid , mob , ghostRole ) ;
if ( + + component . CurrentTakeovers < component . AvailableTakeovers )
{
args . TookRole = true ;
return ;
}
ghostRole . Taken = true ;
if ( component . DeleteOnSpawn )
QueueDel ( uid ) ;
args . TookRole = true ;
}
2023-06-10 22:24:34 +10:00
private bool CanTakeGhost ( EntityUid uid , GhostRoleComponent ? component = null )
{
return Resolve ( uid , ref component , false ) & &
! component . Taken & &
! MetaData ( uid ) . EntityPaused ;
}
2023-04-12 06:32:14 -07:00
private void OnTakeoverTakeRole ( EntityUid uid , GhostTakeoverAvailableComponent component , ref TakeGhostRoleEvent args )
{
if ( ! TryComp ( uid , out GhostRoleComponent ? ghostRole ) | |
2023-06-10 22:24:34 +10:00
! CanTakeGhost ( uid , ghostRole ) )
2023-04-12 06:32:14 -07:00
{
args . TookRole = false ;
return ;
}
ghostRole . Taken = true ;
2023-06-18 11:33:19 -07:00
var mind = EnsureComp < MindContainerComponent > ( uid ) ;
2023-04-12 06:32:14 -07:00
if ( mind . HasMind )
{
args . TookRole = false ;
return ;
}
if ( ghostRole . MakeSentient )
MakeSentientCommand . MakeSentient ( uid , EntityManager , ghostRole . AllowMovement , ghostRole . AllowSpeech ) ;
GhostRoleInternalCreateMindAndTransfer ( args . Player , uid , uid , ghostRole ) ;
2023-10-19 12:34:31 -07:00
UnregisterGhostRole ( ( uid , ghostRole ) ) ;
2023-04-12 06:32:14 -07:00
args . TookRole = true ;
}
2021-02-12 04:35:56 +01:00
}
[AnyCommand]
2022-02-16 00:23:23 -07:00
public sealed class GhostRoles : IConsoleCommand
2021-02-12 04:35:56 +01:00
{
public string Command = > "ghostroles" ;
public string Description = > "Opens the ghost role request window." ;
public string Help = > $"{Command}" ;
public void Execute ( IConsoleShell shell , string argStr , string [ ] args )
{
if ( shell . Player ! = null )
2023-10-28 09:59:53 +11:00
EntitySystem . Get < GhostRoleSystem > ( ) . OpenEui ( shell . Player ) ;
2021-02-12 04:35:56 +01:00
else
shell . WriteLine ( "You can only open the ghost roles UI on a client." ) ;
}
}
}