diff --git a/Content.Client/Changeling/Systems/ChangelingIdentitySystem.cs b/Content.Client/Changeling/Systems/ChangelingIdentitySystem.cs new file mode 100644 index 0000000000..348cfee0f8 --- /dev/null +++ b/Content.Client/Changeling/Systems/ChangelingIdentitySystem.cs @@ -0,0 +1,30 @@ +using Content.Shared.Changeling.Components; +using Content.Shared.Changeling.Systems; +using Robust.Client.GameObjects; + +namespace Content.Client.Changeling.Systems; + +public sealed class ChangelingIdentitySystem : SharedChangelingIdentitySystem +{ + [Dependency] private readonly UserInterfaceSystem _ui = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAfterAutoHandleState); + } + + private void OnAfterAutoHandleState(Entity ent, ref AfterAutoHandleStateEvent args) + { + UpdateUi(ent); + } + + public void UpdateUi(EntityUid uid) + { + if (_ui.TryGetOpenUi(uid, ChangelingTransformUiKey.Key, out var bui)) + { + bui.Update(); + } + } +} diff --git a/Content.Client/Changeling/Transform/ChangelingTransformBoundUserInterface.cs b/Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs similarity index 72% rename from Content.Client/Changeling/Transform/ChangelingTransformBoundUserInterface.cs rename to Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs index 9401231303..8220e18708 100644 --- a/Content.Client/Changeling/Transform/ChangelingTransformBoundUserInterface.cs +++ b/Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs @@ -2,7 +2,7 @@ using JetBrains.Annotations; using Robust.Client.UserInterface; -namespace Content.Client.Changeling.Transform; +namespace Content.Client.Changeling.UI; [UsedImplicitly] public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) @@ -16,16 +16,16 @@ public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owne _window = this.CreateWindow(); _window.OnIdentitySelect += SendIdentitySelect; + + _window.Update(Owner); } - protected override void UpdateState(BoundUserInterfaceState state) + public override void Update() { - base.UpdateState(state); - - if (state is not ChangelingTransformBoundUserInterfaceState current) + if (_window == null) return; - _window?.UpdateState(current); + _window.Update(Owner); } public void SendIdentitySelect(NetEntity identityId) diff --git a/Content.Client/Changeling/Transform/ChangelingTransformMenu.xaml b/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml similarity index 100% rename from Content.Client/Changeling/Transform/ChangelingTransformMenu.xaml rename to Content.Client/Changeling/UI/ChangelingTransformMenu.xaml diff --git a/Content.Client/Changeling/Transform/ChangelingTransformMenu.xaml.cs b/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml.cs similarity index 80% rename from Content.Client/Changeling/Transform/ChangelingTransformMenu.xaml.cs rename to Content.Client/Changeling/UI/ChangelingTransformMenu.xaml.cs index fa2deaf431..ebd4e90440 100644 --- a/Content.Client/Changeling/Transform/ChangelingTransformMenu.xaml.cs +++ b/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml.cs @@ -1,11 +1,11 @@ using System.Numerics; using Content.Client.UserInterface.Controls; -using Content.Shared.Changeling.Systems; +using Content.Shared.Changeling.Components; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; -namespace Content.Client.Changeling.Transform; +namespace Content.Client.Changeling.UI; [GenerateTypedNameReferences] public sealed partial class ChangelingTransformMenu : RadialMenu @@ -19,13 +19,15 @@ public sealed partial class ChangelingTransformMenu : RadialMenu IoCManager.InjectDependencies(this); } - public void UpdateState(ChangelingTransformBoundUserInterfaceState state) + public void Update(EntityUid uid) { Main.DisposeAllChildren(); - foreach (var identity in state.Identites) - { - var identityUid = _entity.GetEntity(identity); + if (!_entity.TryGetComponent(uid, out var identityComp)) + return; + + foreach (var identityUid in identityComp.ConsumedIdentities) + { if (!_entity.TryGetComponent(identityUid, out var metadata)) continue; @@ -48,7 +50,7 @@ public sealed partial class ChangelingTransformMenu : RadialMenu entView.SetEntity(identityUid); button.OnButtonUp += _ => { - OnIdentitySelect?.Invoke(identity); + OnIdentitySelect?.Invoke(_entity.GetNetEntity(identityUid)); Close(); }; button.AddChild(entView); diff --git a/Content.Server/Changeling/Systems/ChangelingIdentitySystem.cs b/Content.Server/Changeling/Systems/ChangelingIdentitySystem.cs new file mode 100644 index 0000000000..8cb3dec3d6 --- /dev/null +++ b/Content.Server/Changeling/Systems/ChangelingIdentitySystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Changeling.Systems; + +namespace Content.Server.Changeling.Systems; + +public sealed class ChangelingIdentitySystem : SharedChangelingIdentitySystem; diff --git a/Content.Shared/Changeling/Components/ChangelingIdentityComponent.cs b/Content.Shared/Changeling/Components/ChangelingIdentityComponent.cs index 2779164e4e..8e74f83537 100644 --- a/Content.Shared/Changeling/Components/ChangelingIdentityComponent.cs +++ b/Content.Shared/Changeling/Components/ChangelingIdentityComponent.cs @@ -8,7 +8,7 @@ namespace Content.Shared.Changeling.Components; /// The storage component for Changelings, it handles the link between a changeling and its consumed identities /// that exist on a paused map. /// -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true)] public sealed partial class ChangelingIdentityComponent : Component { /// diff --git a/Content.Shared/Changeling/Systems/ChangelingDevourSystem.cs b/Content.Shared/Changeling/Systems/ChangelingDevourSystem.cs index a064858d43..500ee06b22 100644 --- a/Content.Shared/Changeling/Systems/ChangelingDevourSystem.cs +++ b/Content.Shared/Changeling/Systems/ChangelingDevourSystem.cs @@ -32,7 +32,7 @@ public sealed class ChangelingDevourSystem : EntitySystem [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly MobStateSystem _mobState = default!; - [Dependency] private readonly ChangelingIdentitySystem _changelingIdentitySystem = default!; + [Dependency] private readonly SharedChangelingIdentitySystem _changelingIdentitySystem = default!; [Dependency] private readonly InventorySystem _inventorySystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; diff --git a/Content.Shared/Changeling/Systems/ChangelingTransformSystem.UI.cs b/Content.Shared/Changeling/Systems/ChangelingTransformSystem.UI.cs index 98926631dc..e555147352 100644 --- a/Content.Shared/Changeling/Systems/ChangelingTransformSystem.UI.cs +++ b/Content.Shared/Changeling/Systems/ChangelingTransformSystem.UI.cs @@ -14,20 +14,8 @@ public sealed class ChangelingTransformIdentitySelectMessage(NetEntity targetIde public readonly NetEntity TargetIdentity = targetIdentity; } -// TODO: Replace with component states. -// We are already networking the ChangelingIdentityComponent, which contains all this information, -// so we can just read it from them from the component and update the UI in an AfterAuotHandleState subscription. [Serializable, NetSerializable] -public sealed class ChangelingTransformBoundUserInterfaceState(List identities) : BoundUserInterfaceState -{ - /// - /// The uids of the cloned identities. - /// - public readonly List Identites = identities; -} - -[Serializable, NetSerializable] -public enum TransformUI : byte +public enum ChangelingTransformUiKey : byte { Key, } diff --git a/Content.Shared/Changeling/Systems/ChangelingTransformSystem.cs b/Content.Shared/Changeling/Systems/ChangelingTransformSystem.cs index 31f22b9294..cf8d9d7cb6 100644 --- a/Content.Shared/Changeling/Systems/ChangelingTransformSystem.cs +++ b/Content.Shared/Changeling/Systems/ChangelingTransformSystem.cs @@ -44,7 +44,7 @@ public sealed partial class ChangelingTransformSystem : EntitySystem _actionsSystem.AddAction(ent, ref ent.Comp.ChangelingTransformActionEntity, ent.Comp.ChangelingTransformAction); var userInterfaceComp = EnsureComp(ent); - _uiSystem.SetUi((ent, userInterfaceComp), TransformUI.Key, new InterfaceData(ChangelingBuiXmlGeneratedName)); + _uiSystem.SetUi((ent, userInterfaceComp), ChangelingTransformUiKey.Key, new InterfaceData(ChangelingBuiXmlGeneratedName)); } private void OnShutdown(Entity ent, ref ComponentShutdown args) @@ -64,18 +64,9 @@ public sealed partial class ChangelingTransformSystem : EntitySystem if (!TryComp(ent, out var userIdentity)) return; - if (!_uiSystem.IsUiOpen((ent, userInterfaceComp), TransformUI.Key, args.Performer)) + if (!_uiSystem.IsUiOpen((ent, userInterfaceComp), ChangelingTransformUiKey.Key, args.Performer)) { - _uiSystem.OpenUi((ent, userInterfaceComp), TransformUI.Key, args.Performer); - - var identityData = new List(); - - foreach (var consumedIdentity in userIdentity.ConsumedIdentities) - { - identityData.Add(GetNetEntity(consumedIdentity)); - } - - _uiSystem.SetUiState((ent, userInterfaceComp), TransformUI.Key, new ChangelingTransformBoundUserInterfaceState(identityData)); + _uiSystem.OpenUi((ent, userInterfaceComp), ChangelingTransformUiKey.Key, args.Performer); } //TODO: Can add a Else here with TransformInto and CloseUI to make a quick switch, // issue right now is that Radials cover the Action buttons so clicking the action closes the UI (due to clicking off a radial causing it to close, even with UI) // but pressing the number does. @@ -108,7 +99,7 @@ public sealed partial class ChangelingTransformSystem : EntitySystem else _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner):player} begun an attempt to transform into \"{Name(targetIdentity)}\""); - var result = _doAfterSystem.TryStartDoAfter(new DoAfterArgs( + _doAfterSystem.TryStartDoAfter(new DoAfterArgs( EntityManager, ent, ent.Comp.TransformWindup, @@ -127,7 +118,7 @@ public sealed partial class ChangelingTransformSystem : EntitySystem private void OnTransformSelected(Entity ent, ref ChangelingTransformIdentitySelectMessage args) { - _uiSystem.CloseUi(ent.Owner, TransformUI.Key, ent); + _uiSystem.CloseUi(ent.Owner, ChangelingTransformUiKey.Key, ent); if (!TryGetEntity(args.TargetIdentity, out var targetIdentity)) return; diff --git a/Content.Shared/Changeling/Systems/ChangelingIdentitySystem.cs b/Content.Shared/Changeling/Systems/SharedChangelingIdentitySystem.cs similarity index 68% rename from Content.Shared/Changeling/Systems/ChangelingIdentitySystem.cs rename to Content.Shared/Changeling/Systems/SharedChangelingIdentitySystem.cs index 8467cc5702..e7e46d79a1 100644 --- a/Content.Shared/Changeling/Systems/ChangelingIdentitySystem.cs +++ b/Content.Shared/Changeling/Systems/SharedChangelingIdentitySystem.cs @@ -2,7 +2,6 @@ using Content.Shared.Changeling.Components; using Content.Shared.Cloning; using Content.Shared.Humanoid; -using Content.Shared.Mind.Components; using Content.Shared.NameModifier.EntitySystems; using Robust.Shared.GameStates; using Robust.Shared.Map; @@ -12,7 +11,7 @@ using Robust.Shared.Prototypes; namespace Content.Shared.Changeling.Systems; -public sealed class ChangelingIdentitySystem : EntitySystem +public abstract class SharedChangelingIdentitySystem : EntitySystem { [Dependency] private readonly INetManager _net = default!; [Dependency] private readonly IPrototypeManager _prototype = default!; @@ -32,22 +31,19 @@ public sealed class ChangelingIdentitySystem : EntitySystem SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnMindAdded); - SubscribeLocalEvent(OnMindRemoved); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); SubscribeLocalEvent(OnStoredRemove); } - private void OnMindAdded(Entity ent, ref MindAddedMessage args) + private void OnPlayerAttached(Entity ent, ref PlayerAttachedEvent args) { - if (!TryComp(args.Container.Owner, out var actor)) - return; - - HandOverPvsOverride(actor.PlayerSession, ent.Comp); + HandOverPvsOverride(ent, args.Player); } - private void OnMindRemoved(Entity ent, ref MindRemovedMessage args) + private void OnPlayerDetached(Entity ent, ref PlayerDetachedEvent args) { - CleanupPvsOverride(ent, args.Container.Owner); + CleanupPvsOverride(ent, args.Player); } private void OnMapInit(Entity ent, ref MapInitEvent args) @@ -59,7 +55,8 @@ public sealed class ChangelingIdentitySystem : EntitySystem private void OnShutdown(Entity ent, ref ComponentShutdown args) { - CleanupPvsOverride(ent, ent.Owner); + if (TryComp(ent, out var actor)) + CleanupPvsOverride(ent, actor.PlayerSession); CleanupChangelingNullspaceIdentities(ent); } @@ -107,66 +104,63 @@ public sealed class ChangelingIdentitySystem : EntitySystem // Movercontrollers and mob collisions are currently being calculated even for paused entities. // Spawning all of them in the same spot causes severe performance problems. // Cryopods and Polymorph have the same problem. - var mob = Spawn(speciesPrototype.Prototype, new MapCoordinates(new Vector2(2 * _numberOfStoredIdentities++, 0), PausedMapId!.Value)); + var clone = Spawn(speciesPrototype.Prototype, new MapCoordinates(new Vector2(2 * _numberOfStoredIdentities++, 0), PausedMapId!.Value)); - var storedIdentity = EnsureComp(mob); + var storedIdentity = EnsureComp(clone); storedIdentity.OriginalEntity = target; // TODO: network this once we have WeakEntityReference or the autonetworking source gen is fixed if (TryComp(target, out var actor)) storedIdentity.OriginalSession = actor.PlayerSession; - _humanoidSystem.CloneAppearance(target, mob); - _cloningSystem.CloneComponents(target, mob, settings); + _humanoidSystem.CloneAppearance(target, clone); + _cloningSystem.CloneComponents(target, clone, settings); var targetName = _nameMod.GetBaseName(target); - _metaSystem.SetEntityName(mob, targetName); - ent.Comp.ConsumedIdentities.Add(mob); + _metaSystem.SetEntityName(clone, targetName); + ent.Comp.ConsumedIdentities.Add(clone); Dirty(ent); - HandlePvsOverride(ent, mob); + HandlePvsOverride(ent, clone); - return mob; + return clone; } /// - /// Simple helper to add a PVS override to a Nullspace Identity + /// Simple helper to add a PVS override to a nullspace identity. /// - /// - /// - private void HandlePvsOverride(EntityUid uid, EntityUid target) + /// The actor that should get the override. + /// The identity stored in nullspace. + private void HandlePvsOverride(EntityUid uid, EntityUid identity) { if (!TryComp(uid, out var actor)) return; - _pvsOverrideSystem.AddSessionOverride(target, actor.PlayerSession); + _pvsOverrideSystem.AddSessionOverride(identity, actor.PlayerSession); } /// - /// Cleanup all Pvs Overrides for the owner of the ChangelingIdentity + /// Cleanup all PVS overrides for the owner of the ChangelingIdentity /// - /// the Changeling itself - /// Who specifically to cleanup from, usually just the same owner, but in the case of a mindswap we want to clean up the victim - private void CleanupPvsOverride(Entity ent, EntityUid entityUid) + /// The changeling storing the identities. + /// + private void CleanupPvsOverride(Entity ent, ICommonSession session) { - if (!TryComp(entityUid, out var actor)) - return; - foreach (var identity in ent.Comp.ConsumedIdentities) { - _pvsOverrideSystem.RemoveSessionOverride(identity, actor.PlayerSession); + _pvsOverrideSystem.RemoveSessionOverride(identity, session); } } /// - /// Inform another Session of the entities stored for Transformation + /// Inform another session of the entities stored for transformation. /// - /// The Session you wish to inform - /// The Target storage of identities - public void HandOverPvsOverride(ICommonSession session, ChangelingIdentityComponent comp) + /// The changeling storing the identities. + /// The session you wish to inform. + public void HandOverPvsOverride(Entity ent, ICommonSession session) { - foreach (var entity in comp.ConsumedIdentities) + foreach (var identity in ent.Comp.ConsumedIdentities) { - _pvsOverrideSystem.AddSessionOverride(entity, session); + _pvsOverrideSystem.AddSessionOverride(identity, session); } }