using JetBrains.Annotations; using Robust.Server.Player; using Robust.Shared.GameObjects; namespace Robust.Server.GameObjects { /// /// System that handles players being attached/detached from entities. /// [UsedImplicitly] public sealed class ActorSystem : EntitySystem { public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnActorPlayerAttach); SubscribeLocalEvent(OnActorPlayerDetach); SubscribeLocalEvent(OnActorShutdown); } private void OnActorPlayerAttach(AttachPlayerEvent args) { args.Result = Attach(args.Uid, args.Player, args.Force, out var forceKicked); args.ForceKicked = forceKicked; } /// /// Attaches a player session to an entity, optionally kicking any sessions already attached to it. /// /// The entity to attach the player to /// The player to attach to the entity /// Whether to kick any existing players from the entity /// Whether the attach succeeded, or not. public bool Attach(EntityUid uid, IPlayerSession player, bool force = false) { return Attach(uid, player, false, out _); } /// /// Attaches a player session to an entity, optionally kicking any sessions already attached to it. /// /// The entity to attach the player to /// The player to attach to the entity /// Whether to kick any existing players from the entity /// The player that was forcefully kicked, or null. /// Whether the attach succeeded, or not. public bool Attach(EntityUid uid, IPlayerSession player, bool force, out IPlayerSession? forceKicked) { // Null by default. forceKicked = null; // Cannot attach to a deleted/nonexisting entity. if (EntityManager.Deleted(uid)) { return false; } // Check if there was a player attached to the entity already... if (EntityManager.TryGetComponent(uid, out ActorComponent actor)) { // If we're not forcing the attach, this fails. if (!force) { return false; } // Set the event's force-kicked session before detaching it. forceKicked = actor.PlayerSession; // This detach cannot fail, as a player is attached to this entity. // It's important to note that detaching the player removes the component. RaiseLocalEvent(uid, new DetachPlayerEvent()); } // We add the actor component. actor = EntityManager.AddComponent(uid); actor.PlayerSession = player; player.SetAttachedEntity(actor.Owner); // The player is fully attached now, raise an event! RaiseLocalEvent(uid, new PlayerAttachedEvent(actor.Owner, player, forceKicked)); return true; } // Not gonna make this method call Detach as all we have to do is remove a component... private void OnActorPlayerDetach(EntityUid uid, ActorComponent component, DetachPlayerEvent args) { // Removing the component will call shutdown, and our subscription will handle the rest of the detach logic. EntityManager.RemoveComponent(uid); args.Result = true; } /// /// Detaches an attached session from the entity, if any. /// /// The entity player sessions will be detached from. /// Whether any player session was detached. public bool Detach(EntityUid entity) { if (!EntityManager.HasComponent(entity)) return false; // Removing the component will call shutdown, and our subscription will handle the rest of the detach logic. EntityManager.RemoveComponent(entity); return true; } /// /// Detaches this player from its attached entity, if any. /// /// The player session that will be detached from any attached entities. /// Whether the player is now detached from any entities. /// This returns true if the player wasn't attached to any entity. public bool Detach(IPlayerSession player) { var uid = player.AttachedEntity; return uid == null || Detach(uid.Value); } private void OnActorShutdown(EntityUid entity, ActorComponent component, ComponentShutdown args) { component.PlayerSession.SetAttachedEntity(null); // The player is fully detached now that the component has shut down. RaiseLocalEvent(entity, new PlayerDetachedEvent(entity, component.PlayerSession)); } } /// /// Raise this broadcast event to attach a player to an entity, optionally detaching the player attached to it. /// public sealed class AttachPlayerEvent : EntityEventArgs { /// /// Player to attach to the entity. /// Input parameter. /// public IPlayerSession Player { get; } /// /// Entity to attach the player to. /// Input parameter. /// public EntityUid Uid { get; } /// /// Whether to force-attach the player, /// detaching any players attached to it if any. /// Input parameter. /// public bool Force { get; } /// /// If the attach was forced and there was a player attached to the entity before, this will be it. /// Output parameter. /// public IPlayerSession? ForceKicked { get; set; } /// /// Whether the player was attached correctly. /// False if not forcing and the entity already had a player attached to it. /// Output parameter. /// public bool Result { get; set; } = false; public AttachPlayerEvent(EntityUid uid, IPlayerSession player, bool force = false) { Uid = uid; Player = player; Force = force; } } /// /// Raise this directed event to detach a player from an entity. /// public sealed class DetachPlayerEvent : EntityEventArgs { /// /// Whether the player was detached correctly. /// Fails if no player was attached to the entity. /// Output parameter. /// public bool Result { get; set; } = false; } /// /// Event for when a player has been attached to an entity. /// public sealed class PlayerAttachedEvent : EntityEventArgs { public EntityUid Entity { get; } public IPlayerSession Player { get; } /// /// The player session that was forcefully kicked from the entity, if any. /// public IPlayerSession? Kicked { get; } public PlayerAttachedEvent(EntityUid entity, IPlayerSession player, IPlayerSession? kicked = null) { Entity = entity; Player = player; Kicked = kicked; } } /// /// Event for when a player has been detached from an entity. /// public sealed class PlayerDetachedEvent : EntityEventArgs { public EntityUid Entity { get; } public IPlayerSession Player { get; } public PlayerDetachedEvent(EntityUid entity, IPlayerSession player) { Entity = entity; Player = player; } } }