Changeling cleanup and bugfix (#39843)

* fixes and cleanup

* key
This commit is contained in:
slarticodefast
2025-08-24 15:50:19 +02:00
committed by GitHub
parent 91a4cee6e1
commit 30aa61c29c
10 changed files with 91 additions and 81 deletions

View File

@@ -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<ChangelingIdentityComponent, AfterAutoHandleStateEvent>(OnAfterAutoHandleState);
}
private void OnAfterAutoHandleState(Entity<ChangelingIdentityComponent> ent, ref AfterAutoHandleStateEvent args)
{
UpdateUi(ent);
}
public void UpdateUi(EntityUid uid)
{
if (_ui.TryGetOpenUi(uid, ChangelingTransformUiKey.Key, out var bui))
{
bui.Update();
}
}
}

View File

@@ -2,7 +2,7 @@
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
namespace Content.Client.Changeling.Transform; namespace Content.Client.Changeling.UI;
[UsedImplicitly] [UsedImplicitly]
public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) 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<ChangelingTransformMenu>(); _window = this.CreateWindow<ChangelingTransformMenu>();
_window.OnIdentitySelect += SendIdentitySelect; _window.OnIdentitySelect += SendIdentitySelect;
_window.Update(Owner);
} }
protected override void UpdateState(BoundUserInterfaceState state) public override void Update()
{ {
base.UpdateState(state); if (_window == null)
if (state is not ChangelingTransformBoundUserInterfaceState current)
return; return;
_window?.UpdateState(current); _window.Update(Owner);
} }
public void SendIdentitySelect(NetEntity identityId) public void SendIdentitySelect(NetEntity identityId)

View File

@@ -1,11 +1,11 @@
using System.Numerics; using System.Numerics;
using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Controls;
using Content.Shared.Changeling.Systems; using Content.Shared.Changeling.Components;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
namespace Content.Client.Changeling.Transform; namespace Content.Client.Changeling.UI;
[GenerateTypedNameReferences] [GenerateTypedNameReferences]
public sealed partial class ChangelingTransformMenu : RadialMenu public sealed partial class ChangelingTransformMenu : RadialMenu
@@ -19,13 +19,15 @@ public sealed partial class ChangelingTransformMenu : RadialMenu
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
} }
public void UpdateState(ChangelingTransformBoundUserInterfaceState state) public void Update(EntityUid uid)
{ {
Main.DisposeAllChildren(); Main.DisposeAllChildren();
foreach (var identity in state.Identites)
{
var identityUid = _entity.GetEntity(identity);
if (!_entity.TryGetComponent<ChangelingIdentityComponent>(uid, out var identityComp))
return;
foreach (var identityUid in identityComp.ConsumedIdentities)
{
if (!_entity.TryGetComponent<MetaDataComponent>(identityUid, out var metadata)) if (!_entity.TryGetComponent<MetaDataComponent>(identityUid, out var metadata))
continue; continue;
@@ -48,7 +50,7 @@ public sealed partial class ChangelingTransformMenu : RadialMenu
entView.SetEntity(identityUid); entView.SetEntity(identityUid);
button.OnButtonUp += _ => button.OnButtonUp += _ =>
{ {
OnIdentitySelect?.Invoke(identity); OnIdentitySelect?.Invoke(_entity.GetNetEntity(identityUid));
Close(); Close();
}; };
button.AddChild(entView); button.AddChild(entView);

View File

@@ -0,0 +1,5 @@
using Content.Shared.Changeling.Systems;
namespace Content.Server.Changeling.Systems;
public sealed class ChangelingIdentitySystem : SharedChangelingIdentitySystem;

View File

@@ -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 /// The storage component for Changelings, it handles the link between a changeling and its consumed identities
/// that exist on a paused map. /// that exist on a paused map.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(raiseAfterAutoHandleState: true)]
public sealed partial class ChangelingIdentityComponent : Component public sealed partial class ChangelingIdentityComponent : Component
{ {
/// <summary> /// <summary>

View File

@@ -32,7 +32,7 @@ public sealed class ChangelingDevourSystem : EntitySystem
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly MobStateSystem _mobState = 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 InventorySystem _inventorySystem = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;

View File

@@ -14,20 +14,8 @@ public sealed class ChangelingTransformIdentitySelectMessage(NetEntity targetIde
public readonly NetEntity TargetIdentity = targetIdentity; 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] [Serializable, NetSerializable]
public sealed class ChangelingTransformBoundUserInterfaceState(List<NetEntity> identities) : BoundUserInterfaceState public enum ChangelingTransformUiKey : byte
{
/// <summary>
/// The uids of the cloned identities.
/// </summary>
public readonly List<NetEntity> Identites = identities;
}
[Serializable, NetSerializable]
public enum TransformUI : byte
{ {
Key, Key,
} }

View File

@@ -44,7 +44,7 @@ public sealed partial class ChangelingTransformSystem : EntitySystem
_actionsSystem.AddAction(ent, ref ent.Comp.ChangelingTransformActionEntity, ent.Comp.ChangelingTransformAction); _actionsSystem.AddAction(ent, ref ent.Comp.ChangelingTransformActionEntity, ent.Comp.ChangelingTransformAction);
var userInterfaceComp = EnsureComp<UserInterfaceComponent>(ent); var userInterfaceComp = EnsureComp<UserInterfaceComponent>(ent);
_uiSystem.SetUi((ent, userInterfaceComp), TransformUI.Key, new InterfaceData(ChangelingBuiXmlGeneratedName)); _uiSystem.SetUi((ent, userInterfaceComp), ChangelingTransformUiKey.Key, new InterfaceData(ChangelingBuiXmlGeneratedName));
} }
private void OnShutdown(Entity<ChangelingTransformComponent> ent, ref ComponentShutdown args) private void OnShutdown(Entity<ChangelingTransformComponent> ent, ref ComponentShutdown args)
@@ -64,18 +64,9 @@ public sealed partial class ChangelingTransformSystem : EntitySystem
if (!TryComp<ChangelingIdentityComponent>(ent, out var userIdentity)) if (!TryComp<ChangelingIdentityComponent>(ent, out var userIdentity))
return; 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); _uiSystem.OpenUi((ent, userInterfaceComp), ChangelingTransformUiKey.Key, args.Performer);
var identityData = new List<NetEntity>();
foreach (var consumedIdentity in userIdentity.ConsumedIdentities)
{
identityData.Add(GetNetEntity(consumedIdentity));
}
_uiSystem.SetUiState((ent, userInterfaceComp), TransformUI.Key, new ChangelingTransformBoundUserInterfaceState(identityData));
} //TODO: Can add a Else here with TransformInto and CloseUI to make a quick switch, } //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) // 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. // but pressing the number does.
@@ -108,7 +99,7 @@ public sealed partial class ChangelingTransformSystem : EntitySystem
else else
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner):player} begun an attempt to transform into \"{Name(targetIdentity)}\""); _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, EntityManager,
ent, ent,
ent.Comp.TransformWindup, ent.Comp.TransformWindup,
@@ -127,7 +118,7 @@ public sealed partial class ChangelingTransformSystem : EntitySystem
private void OnTransformSelected(Entity<ChangelingTransformComponent> ent, private void OnTransformSelected(Entity<ChangelingTransformComponent> ent,
ref ChangelingTransformIdentitySelectMessage args) ref ChangelingTransformIdentitySelectMessage args)
{ {
_uiSystem.CloseUi(ent.Owner, TransformUI.Key, ent); _uiSystem.CloseUi(ent.Owner, ChangelingTransformUiKey.Key, ent);
if (!TryGetEntity(args.TargetIdentity, out var targetIdentity)) if (!TryGetEntity(args.TargetIdentity, out var targetIdentity))
return; return;

View File

@@ -2,7 +2,6 @@
using Content.Shared.Changeling.Components; using Content.Shared.Changeling.Components;
using Content.Shared.Cloning; using Content.Shared.Cloning;
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
using Content.Shared.Mind.Components;
using Content.Shared.NameModifier.EntitySystems; using Content.Shared.NameModifier.EntitySystems;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Map; using Robust.Shared.Map;
@@ -12,7 +11,7 @@ using Robust.Shared.Prototypes;
namespace Content.Shared.Changeling.Systems; namespace Content.Shared.Changeling.Systems;
public sealed class ChangelingIdentitySystem : EntitySystem public abstract class SharedChangelingIdentitySystem : EntitySystem
{ {
[Dependency] private readonly INetManager _net = default!; [Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!; [Dependency] private readonly IPrototypeManager _prototype = default!;
@@ -32,22 +31,19 @@ public sealed class ChangelingIdentitySystem : EntitySystem
SubscribeLocalEvent<ChangelingIdentityComponent, MapInitEvent>(OnMapInit); SubscribeLocalEvent<ChangelingIdentityComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<ChangelingIdentityComponent, ComponentShutdown>(OnShutdown); SubscribeLocalEvent<ChangelingIdentityComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<ChangelingIdentityComponent, MindAddedMessage>(OnMindAdded); SubscribeLocalEvent<ChangelingIdentityComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<ChangelingIdentityComponent, MindRemovedMessage>(OnMindRemoved); SubscribeLocalEvent<ChangelingIdentityComponent, PlayerDetachedEvent>(OnPlayerDetached);
SubscribeLocalEvent<ChangelingStoredIdentityComponent, ComponentRemove>(OnStoredRemove); SubscribeLocalEvent<ChangelingStoredIdentityComponent, ComponentRemove>(OnStoredRemove);
} }
private void OnMindAdded(Entity<ChangelingIdentityComponent> ent, ref MindAddedMessage args) private void OnPlayerAttached(Entity<ChangelingIdentityComponent> ent, ref PlayerAttachedEvent args)
{ {
if (!TryComp<ActorComponent>(args.Container.Owner, out var actor)) HandOverPvsOverride(ent, args.Player);
return;
HandOverPvsOverride(actor.PlayerSession, ent.Comp);
} }
private void OnMindRemoved(Entity<ChangelingIdentityComponent> ent, ref MindRemovedMessage args) private void OnPlayerDetached(Entity<ChangelingIdentityComponent> ent, ref PlayerDetachedEvent args)
{ {
CleanupPvsOverride(ent, args.Container.Owner); CleanupPvsOverride(ent, args.Player);
} }
private void OnMapInit(Entity<ChangelingIdentityComponent> ent, ref MapInitEvent args) private void OnMapInit(Entity<ChangelingIdentityComponent> ent, ref MapInitEvent args)
@@ -59,7 +55,8 @@ public sealed class ChangelingIdentitySystem : EntitySystem
private void OnShutdown(Entity<ChangelingIdentityComponent> ent, ref ComponentShutdown args) private void OnShutdown(Entity<ChangelingIdentityComponent> ent, ref ComponentShutdown args)
{ {
CleanupPvsOverride(ent, ent.Owner); if (TryComp<ActorComponent>(ent, out var actor))
CleanupPvsOverride(ent, actor.PlayerSession);
CleanupChangelingNullspaceIdentities(ent); CleanupChangelingNullspaceIdentities(ent);
} }
@@ -107,66 +104,63 @@ public sealed class ChangelingIdentitySystem : EntitySystem
// Movercontrollers and mob collisions are currently being calculated even for paused entities. // Movercontrollers and mob collisions are currently being calculated even for paused entities.
// Spawning all of them in the same spot causes severe performance problems. // Spawning all of them in the same spot causes severe performance problems.
// Cryopods and Polymorph have the same problem. // 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<ChangelingStoredIdentityComponent>(mob); var storedIdentity = EnsureComp<ChangelingStoredIdentityComponent>(clone);
storedIdentity.OriginalEntity = target; // TODO: network this once we have WeakEntityReference or the autonetworking source gen is fixed storedIdentity.OriginalEntity = target; // TODO: network this once we have WeakEntityReference or the autonetworking source gen is fixed
if (TryComp<ActorComponent>(target, out var actor)) if (TryComp<ActorComponent>(target, out var actor))
storedIdentity.OriginalSession = actor.PlayerSession; storedIdentity.OriginalSession = actor.PlayerSession;
_humanoidSystem.CloneAppearance(target, mob); _humanoidSystem.CloneAppearance(target, clone);
_cloningSystem.CloneComponents(target, mob, settings); _cloningSystem.CloneComponents(target, clone, settings);
var targetName = _nameMod.GetBaseName(target); var targetName = _nameMod.GetBaseName(target);
_metaSystem.SetEntityName(mob, targetName); _metaSystem.SetEntityName(clone, targetName);
ent.Comp.ConsumedIdentities.Add(mob); ent.Comp.ConsumedIdentities.Add(clone);
Dirty(ent); Dirty(ent);
HandlePvsOverride(ent, mob); HandlePvsOverride(ent, clone);
return mob; return clone;
} }
/// <summary> /// <summary>
/// Simple helper to add a PVS override to a Nullspace Identity /// Simple helper to add a PVS override to a nullspace identity.
/// </summary> /// </summary>
/// <param name="uid"></param> /// <param name="uid">The actor that should get the override.</param>
/// <param name="target"></param> /// <param name="identity">The identity stored in nullspace.</param>
private void HandlePvsOverride(EntityUid uid, EntityUid target) private void HandlePvsOverride(EntityUid uid, EntityUid identity)
{ {
if (!TryComp<ActorComponent>(uid, out var actor)) if (!TryComp<ActorComponent>(uid, out var actor))
return; return;
_pvsOverrideSystem.AddSessionOverride(target, actor.PlayerSession); _pvsOverrideSystem.AddSessionOverride(identity, actor.PlayerSession);
} }
/// <summary> /// <summary>
/// Cleanup all Pvs Overrides for the owner of the ChangelingIdentity /// Cleanup all PVS overrides for the owner of the ChangelingIdentity
/// </summary> /// </summary>
/// <param name="ent">the Changeling itself</param> /// <param name="ent">The changeling storing the identities.</param>
/// <param name="entityUid">Who specifically to cleanup from, usually just the same owner, but in the case of a mindswap we want to clean up the victim</param> /// <param name="entityUid"The session you wish to remove the overrides from.</param>
private void CleanupPvsOverride(Entity<ChangelingIdentityComponent> ent, EntityUid entityUid) private void CleanupPvsOverride(Entity<ChangelingIdentityComponent> ent, ICommonSession session)
{ {
if (!TryComp<ActorComponent>(entityUid, out var actor))
return;
foreach (var identity in ent.Comp.ConsumedIdentities) foreach (var identity in ent.Comp.ConsumedIdentities)
{ {
_pvsOverrideSystem.RemoveSessionOverride(identity, actor.PlayerSession); _pvsOverrideSystem.RemoveSessionOverride(identity, session);
} }
} }
/// <summary> /// <summary>
/// Inform another Session of the entities stored for Transformation /// Inform another session of the entities stored for transformation.
/// </summary> /// </summary>
/// <param name="session">The Session you wish to inform</param> /// <param name="ent">The changeling storing the identities.</param>
/// <param name="comp">The Target storage of identities</param> /// <param name="session">The session you wish to inform.</param>
public void HandOverPvsOverride(ICommonSession session, ChangelingIdentityComponent comp) public void HandOverPvsOverride(Entity<ChangelingIdentityComponent> ent, ICommonSession session)
{ {
foreach (var entity in comp.ConsumedIdentities) foreach (var identity in ent.Comp.ConsumedIdentities)
{ {
_pvsOverrideSystem.AddSessionOverride(entity, session); _pvsOverrideSystem.AddSessionOverride(identity, session);
} }
} }