@@ -1,7 +1,6 @@
using System.Diagnostics.CodeAnalysis ;
using Content.Server.Administration.Logs ;
using Content.Server.GameTicking ;
using Content.Server.Mind.Commands ;
using Content.Shared.Database ;
using Content.Shared.Ghost ;
using Content.Shared.Mind ;
@@ -11,9 +10,7 @@ using Robust.Server.GameObjects;
using Robust.Server.GameStates ;
using Robust.Server.Player ;
using Robust.Shared.Map ;
using Robust.Shared.Map.Components ;
using Robust.Shared.Network ;
using Robust.Shared.Player ;
using Robust.Shared.Timing ;
using Robust.Shared.Utility ;
@@ -49,14 +46,20 @@ public sealed class MindSystem : SharedMindSystem
mind . UserId = null ;
}
if ( mind . OwnedEntity ! = null & & ! TerminatingOrDeleted ( mind . OwnedEntity . Value ) )
TransferTo ( uid , null , mind : mind , createGhost : false ) ;
if ( ! TryComp ( mind . OwnedEntity , out MetaDataComponent ? meta ) | | meta . EntityLifeStage > = EntityLifeStage . Terminating )
return ;
RaiseLocalEvent ( mind . OwnedEntity . Value , new MindRemovedMessage ( uid , mind ) , true ) ;
mind . OwnedEntity = null ;
mind . OwnedComponent = null ;
}
private void OnMindContainerTerminating ( EntityUid uid , MindContainerComponent component , ref EntityTerminatingEvent args )
{
// Let's not create ghosts if not in the middle of the round.
if ( _gameTicker . RunLevel = = GameRunLevel . PreRoundLobby )
return ;
if ( ! TryGetMind ( uid , out var mindId , out var mind , component ) )
return ;
@@ -73,45 +76,46 @@ public sealed class MindSystem : SharedMindSystem
}
TransferTo ( mindId , null , createGhost : false , mind : mind ) ;
DebugTools . AssertNull ( mind . OwnedEntity ) ;
if ( ! component . GhostOnShutdown | | mind . Session = = null | | _gameTicker . RunLevel = = GameRunLevel . PreRoundLobby )
return ;
var xform = Transform ( uid ) ;
var gridId = xform . GridUid ;
var spawnPosition = Transform ( uid ) . Coordinates ;
// Use a regular timer here because the entity has probably been deleted.
Timer . Spawn ( 0 , ( ) = >
if ( component . GhostOnShutdown & & mind . Session ! = null )
{
// Make extra sure the round didn't end between spawning the timer and it being executed.
if ( _gameTicker . RunLevel = = GameRunLevel . PreRoundLobby )
return ;
var xform = Transform ( uid ) ;
var gridId = xform . GridUid ;
var spawnPosition = Transform ( uid ) . Coordinates ;
// Async this so that we don't throw if the grid we're on is being deleted.
if ( ! HasComp < MapGridComponent > ( gridId ) )
spawnPosition = _gameTicker . GetObserverSpawnPoint ( ) ;
// TODO refactor observer spawning.
// please.
if ( ! spawnPosition . IsValid ( EntityManager ) )
// Use a regular timer here because the entity has probably been deleted.
Timer . Spawn ( 0 , ( ) = >
{
// This should be an error, if it didn't cause tests to start error ing w hen they delete a player .
Log . Warning ( $"Entity \" { ToPrettyString ( uid ) } \ " for {mind.CharacterName} was deleted, and no applicable spawn location is available." ) ;
TransferTo ( mindId , null , createGhost : false , mind : mind ) ;
return ;
}
// Make extra sure the round didn't end between spawn ing t he timer and it being executed .
if ( _gameTicker . RunLevel = = GameRunLevel . PreRoundLobby )
return ;
var ghost = Spawn ( GameTicker . ObserverPrototypeName , spawnPosition ) ;
var ghostComponent = Comp < GhostComponent > ( ghost ) ;
_ghosts . SetCanReturnToBody ( ghostComponent , false ) ;
// Async this so that we don't throw if the grid we're on is being deleted.
if ( ! _maps . GridExists ( gridId ) )
spawnPosition = _gameTicker . GetObserverSpawnPoint ( ) ;
// Log these to make sure they're not causing the GameTicker round restart bugs.. .
Log . Debug ( $"Entity \" { ToPrettyString ( uid ) } \ " for {mind.CharacterName} was deleted, spawned \"{ToPrettyString(ghost)}\"." ) ;
_metaData . SetEntityName ( ghost , mind . CharacterName ? ? string . Empty ) ;
TransferTo ( mindId , ghost , mind : mind ) ;
} ) ;
// TODO refactor observer spawning .
// please.
if ( ! spawnPosition . IsValid ( EntityManager ) )
{
// This should be an error, if it didn't cause tests to start erroring when they delete a player.
Log . Warning ( $"Entity \" { ToPrettyString ( uid ) } \ " for {mind.CharacterName} was deleted, and no applicable spawn location is available." ) ;
TransferTo ( mindId , null , createGhost : false , mind : mind ) ;
return ;
}
var ghost = Spawn ( "MobObserver" , spawnPosition ) ;
var ghostComponent = Comp < GhostComponent > ( ghost ) ;
_ghosts . SetCanReturnToBody ( ghostComponent , false ) ;
// Log these to make sure they're not causing the GameTicker round restart bugs...
Log . Debug ( $"Entity \" { ToPrettyString ( uid ) } \ " for {mind.CharacterName} was deleted, spawned \"{ToPrettyString(ghost)}\"." ) ;
var val = mind . CharacterName ? ? string . Empty ;
_metaData . SetEntityName ( ghost , val ) ;
TransferTo ( mindId , ghost , mind : mind ) ;
} ) ;
}
}
public override bool TryGetMind ( NetUserId user , [ NotNullWhen ( true ) ] out EntityUid ? mindId , [ NotNullWhen ( true ) ] out MindComponent ? mind )
@@ -126,18 +130,18 @@ public sealed class MindSystem : SharedMindSystem
return false ;
}
public bool TryGetSession ( EntityUid ? mindId , [ NotNullWhen ( true ) ] out ICommon Session ? session )
public bool TryGetSession ( EntityUid ? mindId , [ NotNullWhen ( true ) ] out IPlayer Session ? session )
{
session = null ;
return TryComp ( mindId , out MindComponent ? mind ) & & ( session = mind . Session ) ! = null ;
return TryComp ( mindId , out MindComponent ? mind ) & & ( session = ( IPlayerSession ? ) mind . Session ) ! = null ;
}
public ICommon Session ? GetSession ( MindComponent mind )
public IPlayer Session ? GetSession ( MindComponent mind )
{
return mind . Session ;
return ( IPlayerSession ? ) mind . Session ;
}
public bool TryGetSession ( MindComponent mind , [ NotNullWhen ( true ) ] out ICommon Session ? session )
public bool TryGetSession ( MindComponent mind , [ NotNullWhen ( true ) ] out IPlayer Session ? session )
{
return ( session = GetSession ( mind ) ) ! = null ;
}
@@ -175,9 +179,7 @@ public sealed class MindSystem : SharedMindSystem
return ;
}
if ( GetSession ( mind ) is { } session )
_actor . Attach ( entity , session ) ;
GetSession ( mind ) ? . AttachToEntity ( entity ) ;
mind . VisitingEntity = entity ;
// EnsureComp instead of AddComp to deal with deferred deletions.
@@ -196,14 +198,13 @@ public sealed class MindSystem : SharedMindSystem
if ( mind . VisitingEntity = = null )
return ;
RemoveVisitingEntity ( mindId , mind) ;
RemoveVisitingEntity ( mind ) ;
if ( mind . Session = = null | | mind . Session . AttachedEntity = = mind . VisitingEntity )
return ;
var owned = mind . OwnedEntity ;
if ( GetSession ( mind ) is { } session )
_actor . Attach ( owned , session ) ;
GetSession ( mind ) ? . AttachToEntity ( owned ) ;
if ( owned . HasValue )
{
@@ -218,10 +219,11 @@ public sealed class MindSystem : SharedMindSystem
if ( mind = = null & & ! Resolve ( mindId , ref mind ) )
return ;
base . TransferTo ( mindId , entity , ghostCheckOverride , createGhost , mind ) ;
if ( entity = = mind . OwnedEntity )
return ;
Dirty ( mindId , mind ) ;
MindContainerComponent ? component = null ;
var alreadyAttached = false ;
@@ -245,33 +247,27 @@ public sealed class MindSystem : SharedMindSystem
}
else if ( createGhost )
{
// TODO remove this option.
// Transfer-to-null should just detach a mind.
// If people want to create a ghost, that should be done explicitly via some TransferToGhost() method, not
// not implicitly via optional arguments.
var position = Deleted ( mind . OwnedEntity )
? _gameTicker . GetObserverSpawnPoint ( ) . ToMap ( EntityManager , _transform )
: Transform ( mind . OwnedEntity . Value ) . MapPosition ;
entity = Spawn ( "MobObserver" , position ) ;
component = EnsureComp < MindContainerComponent > ( entity . Value ) ;
var ghostComponent = Comp < GhostComponent > ( entity . Value ) ;
_ghosts . SetCanReturnToBody ( ghostComponent , false ) ;
}
var oldComp = mind . OwnedComponent ;
var oldEntity = mind . OwnedEntity ;
if ( Try Comp( oldEntity , out MindContainerComponent ? oldContainer ) )
if ( old Comp ! = null & & oldEntity ! = null )
{
oldContainer . Mind = null ;
mind . OwnedEntity = null ;
Entity < MindComponent > mindEnt = ( mindId , mind ) ;
Entity < MindContainerComponent > containerEnt = ( oldEntity . Value , oldContainer ) ;
RaiseLocalEvent ( oldEntity . Value , new MindRemovedMessage ( mindEnt , containerEnt ) ) ;
RaiseLocalEvent ( mindId , new MindGotRemovedEvent ( mindEnt , containerEnt ) ) ;
Dirty ( oldEntity . Value , oldContainer ) ;
if ( oldComp . Mind ! = null )
_pvsOverride . ClearOverride ( oldComp . Mind . Value ) ;
oldComp . Mind = null ;
RaiseLocalEvent ( oldEntity . Value , new MindRemovedMessage ( oldEntity . Value , mind ) , true ) ;
}
SetOwnedEntity ( mind , entity , component ) ;
// Don't do the full deletion cleanup if we're transferring to our VisitingEntity
if ( alreadyAttached )
{
@@ -285,7 +281,7 @@ public sealed class MindSystem : SharedMindSystem
| | ! TryComp ( mind . VisitingEntity ! , out GhostComponent ? ghostComponent ) // visiting entity is not a Ghost
| | ! ghostComponent . CanReturnToBody ) ) // it is a ghost, but cannot return to body anyway, so it's okay
{
RemoveVisitingEntity ( mindId , mind) ;
RemoveVisitingEntity ( mind ) ;
}
// Player is CURRENTLY connected.
@@ -293,20 +289,14 @@ public sealed class MindSystem : SharedMindSystem
if ( session ! = null & & ! alreadyAttached & & mind . VisitingEntity = = null )
{
_actor . Attach ( entity , session , true ) ;
DebugTools . Assert ( session . AttachedEntity = = entity , $"Failed to attach entity." ) ;
Log . Info ( $"Session {session.Name} transferred to entity {entity}." ) ;
}
if ( entity ! = null )
if ( mind . OwnedCompon ent ! = null )
{
c omponent! .Mind = mindId ;
mind . OwnedEntity = entity ;
mind . OriginalOwnedEntity ? ? = GetNetEntity ( mind. OwnedEntity ) ;
Entity < MindComponent > mindEnt = ( mindId , mind ) ;
Entity < MindContainerComponent > containerEnt = ( entity . Value , component ) ;
RaiseLocalEvent ( entity . Value , new MindAddedMessage ( mindEnt , containerEnt ) ) ;
RaiseLocalEvent ( mindId , new MindGotAddedEvent ( mindEnt , containerEnt ) ) ;
Dirty ( entity . Value , component ) ;
mind . OwnedC omponent. Mind = mindId ;
RaiseLocalEvent ( mind. OwnedEntity ! . Value , new MindAddedMessage ( ) , true ) ;
mind . OriginalOwnedEntity ? ? = mind . OwnedEntity ;
}
}
@@ -323,7 +313,6 @@ public sealed class MindSystem : SharedMindSystem
if ( mind . UserId = = userId )
return ;
Dirty ( mindId , mind ) ;
_pvsOverride . ClearOverride ( mindId ) ;
if ( userId ! = null & & ! _players . TryGetPlayerData ( userId . Value , out _ ) )
{
@@ -374,27 +363,4 @@ public sealed class MindSystem : SharedMindSystem
if ( _players . GetPlayerData ( userId . Value ) . ContentData ( ) is { } data )
data . Mind = mindId ;
}
public void ControlMob ( EntityUid user , EntityUid target )
{
if ( TryComp ( user , out ActorComponent ? actor ) )
ControlMob ( actor . PlayerSession . UserId , target ) ;
}
public void ControlMob ( NetUserId user , EntityUid target )
{
var ( mindId , mind ) = GetOrCreateMind ( user ) ;
if ( mind . CurrentEntity = = target )
return ;
if ( mind . OwnedEntity = = target )
{
UnVisit ( mindId , mind ) ;
return ;
}
MakeSentientCommand . MakeSentient ( target , EntityManager ) ;
TransferTo ( mindId , target , ghostCheckOverride : true , mind : mind ) ;
}
}