]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Cleanup subdermal implant code (#39755)
authorslarticodefast <161409025+slarticodefast@users.noreply.github.com>
Wed, 20 Aug 2025 11:52:03 +0000 (13:52 +0200)
committerGitHub <noreply@github.com>
Wed, 20 Aug 2025 11:52:03 +0000 (13:52 +0200)
20 files changed:
Content.Client/Implants/ImplanterSystem.cs
Content.Client/Implants/SubdermalImplantSystem.cs [new file with mode: 0644]
Content.Server/Implants/ChameleonControllerSystem.cs
Content.Server/Implants/ImplanterSystem.cs
Content.Server/Implants/RadioImplantSystem.cs
Content.Server/Implants/SubdermalImplantSystem.cs
Content.Server/Mindshield/MindShieldSystem.cs
Content.Shared/Implants/Components/ReplacementImplantComponent.cs [new file with mode: 0644]
Content.Shared/Implants/Components/StorageImplantComponent.cs [new file with mode: 0644]
Content.Shared/Implants/Components/SubdermalImplantComponent.cs
Content.Shared/Implants/ReplacementImplantSystem.cs [new file with mode: 0644]
Content.Shared/Implants/SharedImplanterSystem.cs
Content.Shared/Implants/SharedSubdermalImplantSystem.Relays.cs [new file with mode: 0644]
Content.Shared/Implants/SharedSubdermalImplantSystem.cs
Content.Shared/Implants/StorageImplantSystem.cs [new file with mode: 0644]
Content.Shared/Mindshield/FakeMindShield/FakeMindshieldSystem.cs [moved from Content.Shared/Mindshield/FakeMindShield/SharedFakeMindshieldSystem.cs with 94% similarity]
Content.Shared/Mindshield/FakeMindShield/SharedFakeMindShieldImplantSystem.cs [deleted file]
Resources/Prototypes/Actions/types.yml
Resources/Prototypes/Entities/Objects/Misc/implanters.yml
Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml

index cca09f5dad1d273371b2fab53c9a31c148447a85..4ba4d015ca576b78792dd13331876e0ba923695d 100644 (file)
@@ -23,6 +23,8 @@ public sealed class ImplanterSystem : SharedImplanterSystem
     {
         if (_uiSystem.TryGetOpenUi<DeimplantBoundUserInterface>(uid, DeimplantUiKey.Key, out var bui))
         {
+            // TODO: Don't use protoId for deimplanting
+            // and especially not raw strings!
             Dictionary<string, string> implants = new();
             foreach (var implant in component.DeimplantWhitelist)
             {
diff --git a/Content.Client/Implants/SubdermalImplantSystem.cs b/Content.Client/Implants/SubdermalImplantSystem.cs
new file mode 100644 (file)
index 0000000..5d814ed
--- /dev/null
@@ -0,0 +1,5 @@
+using Content.Shared.Implants;
+
+namespace Content.Client.Implants;
+
+public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem;
index 9e876f939904f56db5104a4c0589154c1b3c1959..930f2e3156f81acbf8b995ba5e2a64a9d1d4f872 100644 (file)
@@ -29,17 +29,17 @@ public sealed class ChameleonControllerSystem : SharedChameleonControllerSystem
     {
         base.Initialize();
 
-        SubscribeLocalEvent<SubdermalImplantComponent, ChameleonControllerSelectedOutfitMessage>(OnSelected);
+        SubscribeLocalEvent<ChameleonControllerImplantComponent, ChameleonControllerSelectedOutfitMessage>(OnSelected);
 
         SubscribeLocalEvent<ChameleonClothingComponent, InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent>>(ChameleonControllerOutfitItemSelected);
     }
 
-    private void OnSelected(Entity<SubdermalImplantComponent> ent, ref ChameleonControllerSelectedOutfitMessage args)
+    private void OnSelected(Entity<ChameleonControllerImplantComponent> ent, ref ChameleonControllerSelectedOutfitMessage args)
     {
-        if (!_delay.TryResetDelay(ent.Owner, true) || ent.Comp.ImplantedEntity == null || !HasComp<ChameleonControllerImplantComponent>(ent))
+        if (!TryComp<SubdermalImplantComponent>(ent, out var implantComp) || implantComp.ImplantedEntity == null || !_delay.TryResetDelay(ent.Owner, true))
             return;
 
-        ChangeChameleonClothingToOutfit(ent.Comp.ImplantedEntity.Value, args.SelectedChameleonOutfit);
+        ChangeChameleonClothingToOutfit(implantComp.ImplantedEntity.Value, args.SelectedChameleonOutfit);
     }
 
     /// <summary>
index 5023b1b3e4961c0b973ec6d3522456cd1908d77c..48e83e4878cc069fb49ca457e715358684b9a6db 100644 (file)
@@ -27,6 +27,7 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
         SubscribeLocalEvent<ImplanterComponent, DrawEvent>(OnDraw);
     }
 
+    // TODO: This all needs to be moved to shared and predicted.
     private void OnImplanterAfterInteract(EntityUid uid, ImplanterComponent component, AfterInteractEvent args)
     {
         if (args.Target == null || !args.CanReach || args.Handled)
index d9ba2290574e60df098230e587effa97147f1563..c5ae1ce49411a09a51a85c4eed873f202dea3e44 100644 (file)
@@ -1,7 +1,6 @@
 using Content.Server.Radio.Components;
 using Content.Shared.Implants;
 using Content.Shared.Implants.Components;
-using Robust.Shared.Containers;
 
 namespace Content.Server.Implants;
 
@@ -12,7 +11,7 @@ public sealed class RadioImplantSystem : EntitySystem
         base.Initialize();
 
         SubscribeLocalEvent<RadioImplantComponent, ImplantImplantedEvent>(OnImplantImplanted);
-        SubscribeLocalEvent<RadioImplantComponent, EntGotRemovedFromContainerMessage>(OnRemove);
+        SubscribeLocalEvent<RadioImplantComponent, ImplantRemovedEvent>(OnImplantRemoved);
     }
 
     /// <summary>
@@ -20,19 +19,16 @@ public sealed class RadioImplantSystem : EntitySystem
     /// </summary>
     private void OnImplantImplanted(Entity<RadioImplantComponent> ent, ref ImplantImplantedEvent args)
     {
-        if (args.Implanted == null)
-            return;
-
-        var activeRadio = EnsureComp<ActiveRadioComponent>(args.Implanted.Value);
+        var activeRadio = EnsureComp<ActiveRadioComponent>(args.Implanted);
         foreach (var channel in ent.Comp.RadioChannels)
         {
             if (activeRadio.Channels.Add(channel))
                 ent.Comp.ActiveAddedChannels.Add(channel);
         }
 
-        EnsureComp<IntrinsicRadioReceiverComponent>(args.Implanted.Value);
+        EnsureComp<IntrinsicRadioReceiverComponent>(args.Implanted);
 
-        var intrinsicRadioTransmitter = EnsureComp<IntrinsicRadioTransmitterComponent>(args.Implanted.Value);
+        var intrinsicRadioTransmitter = EnsureComp<IntrinsicRadioTransmitterComponent>(args.Implanted);
         foreach (var channel in ent.Comp.RadioChannels)
         {
             if (intrinsicRadioTransmitter.Channels.Add(channel))
@@ -43,9 +39,9 @@ public sealed class RadioImplantSystem : EntitySystem
     /// <summary>
     /// Removes intrinsic radio components once the Radio Implant is removed
     /// </summary>
-    private void OnRemove(Entity<RadioImplantComponent> ent, ref EntGotRemovedFromContainerMessage args)
+    private void OnImplantRemoved(Entity<RadioImplantComponent> ent, ref ImplantRemovedEvent args)
     {
-        if (TryComp<ActiveRadioComponent>(args.Container.Owner, out var activeRadioComponent))
+        if (TryComp<ActiveRadioComponent>(args.Implanted, out var activeRadioComponent))
         {
             foreach (var channel in ent.Comp.ActiveAddedChannels)
             {
@@ -55,11 +51,11 @@ public sealed class RadioImplantSystem : EntitySystem
 
             if (activeRadioComponent.Channels.Count == 0)
             {
-                RemCompDeferred<ActiveRadioComponent>(args.Container.Owner);
+                RemCompDeferred<ActiveRadioComponent>(args.Implanted);
             }
         }
 
-        if (!TryComp<IntrinsicRadioTransmitterComponent>(args.Container.Owner, out var radioTransmitterComponent))
+        if (!TryComp<IntrinsicRadioTransmitterComponent>(args.Implanted, out var radioTransmitterComponent))
             return;
 
         foreach (var channel in ent.Comp.TransmitterAddedChannels)
@@ -70,7 +66,7 @@ public sealed class RadioImplantSystem : EntitySystem
 
         if (radioTransmitterComponent.Channels.Count == 0 || activeRadioComponent?.Channels.Count == 0)
         {
-            RemCompDeferred<IntrinsicRadioTransmitterComponent>(args.Container.Owner);
+            RemCompDeferred<IntrinsicRadioTransmitterComponent>(args.Implanted);
         }
     }
 }
index f0530358a6e24af727d991a84acea7d25a14d5a7..582b9cb2ac4a11bf434e35aebfd908e0a71f7a8b 100644 (file)
@@ -18,6 +18,7 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
         SubscribeLocalEvent<StoreComponent, ImplantRelayEvent<AfterInteractUsingEvent>>(OnStoreRelay);
     }
 
+    // TODO: This shouldn't be in the SubdermalImplantSystem
     private void OnStoreRelay(EntityUid uid, StoreComponent store, ImplantRelayEvent<AfterInteractUsingEvent> implantRelay)
     {
         var args = implantRelay.Event;
index c04fb12027dd8254759fbbb56ec72ca18aba94af..bc5b65159b862b963528928c8cbd3896557d6e02 100644 (file)
@@ -27,7 +27,7 @@ public sealed class MindShieldSystem : EntitySystem
         base.Initialize();
 
         SubscribeLocalEvent<MindShieldImplantComponent, ImplantImplantedEvent>(OnImplantImplanted);
-        SubscribeLocalEvent<MindShieldImplantComponent, EntGotRemovedFromContainerMessage>(OnImplantDraw);
+        SubscribeLocalEvent<MindShieldImplantComponent, ImplantRemovedEvent>(OnImplantRemoved);
     }
 
     private void OnImplantImplanted(Entity<MindShieldImplantComponent> ent, ref ImplantImplantedEvent ev)
@@ -35,8 +35,8 @@ public sealed class MindShieldSystem : EntitySystem
         if (ev.Implanted == null)
             return;
 
-        EnsureComp<MindShieldComponent>(ev.Implanted.Value);
-        MindShieldRemovalCheck(ev.Implanted.Value, ev.Implant);
+        EnsureComp<MindShieldComponent>(ev.Implanted);
+        MindShieldRemovalCheck(ev.Implanted, ev.Implant);
     }
 
     /// <summary>
@@ -58,9 +58,9 @@ public sealed class MindShieldSystem : EntitySystem
         }
     }
 
-    private void OnImplantDraw(Entity<MindShieldImplantComponent> ent, ref EntGotRemovedFromContainerMessage args)
+    private void OnImplantRemoved(Entity<MindShieldImplantComponent> ent, ref ImplantRemovedEvent args)
     {
-        RemComp<MindShieldComponent>(args.Container.Owner);
+        RemComp<MindShieldComponent>(args.Implanted);
     }
 }
 
diff --git a/Content.Shared/Implants/Components/ReplacementImplantComponent.cs b/Content.Shared/Implants/Components/ReplacementImplantComponent.cs
new file mode 100644 (file)
index 0000000..73a6d4d
--- /dev/null
@@ -0,0 +1,18 @@
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Implants.Components;
+
+/// <summary>
+/// Added to implants with the see <see cref="SubdermalImplantComponent"/>.
+/// When implanted it will cause other implants in the whitelist to be deleted and thus replaced.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ReplacementImplantComponent : Component
+{
+    /// <summary>
+    /// Whitelist for which implants to delete.
+    /// </summary>
+    [DataField(required: true)]
+    public EntityWhitelist Whitelist = new();
+}
diff --git a/Content.Shared/Implants/Components/StorageImplantComponent.cs b/Content.Shared/Implants/Components/StorageImplantComponent.cs
new file mode 100644 (file)
index 0000000..289f01b
--- /dev/null
@@ -0,0 +1,11 @@
+using Content.Shared.Storage;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Implants.Components;
+
+/// <summary>
+/// Handles emptying the implant's <see cref="StorageComponent"/> when the implant is removed.
+/// Without this the contents would be deleted.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class StorageImplantComponent : Component;
index 390d113dfbd4058edba072dc72253e1a57452b90..85f26726033e2aea0fc68906563e3111d0685911 100644 (file)
@@ -16,13 +16,21 @@ public sealed partial class SubdermalImplantComponent : Component
     /// <summary>
     /// Used where you want the implant to grant the owner an instant action.
     /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    [DataField("implantAction")]
+    [DataField]
     public EntProtoId? ImplantAction;
 
+    /// <summary>
+    /// The provided action entity.
+    /// </summary>
     [DataField, AutoNetworkedField]
     public EntityUid? Action;
 
+    /// <summary>
+    /// Components to add/remove to the implantee when the implant is injected/extracted.
+    /// </summary>
+    [DataField]
+    public ComponentRegistry ImplantComponents = new();
+
     /// <summary>
     /// The entity this implant is inside
     /// </summary>
@@ -32,8 +40,7 @@ public sealed partial class SubdermalImplantComponent : Component
     /// <summary>
     /// Should this implant be removeable?
     /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    [DataField("permanent"), AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public bool Permanent = false;
 
     /// <summary>
@@ -61,23 +68,20 @@ public sealed partial class SubdermalImplantComponent : Component
 /// <summary>
 /// Used for opening the storage implant via action.
 /// </summary>
-public sealed partial class OpenStorageImplantEvent : InstantActionEvent
-{
-
-}
+/// <remarks>
+/// TODO: Delete this and just add a ToggleUIOnTriggerComponent
+/// </remarks>
+public sealed partial class OpenStorageImplantEvent : InstantActionEvent;
 
 /// <summary>
 /// Used for triggering trigger events on the implant via action
 /// </summary>
-public sealed partial class ActivateImplantEvent : InstantActionEvent
-{
-
-}
+public sealed partial class ActivateImplantEvent : InstantActionEvent;
 
 /// <summary>
 /// Used for opening the uplink implant via action.
 /// </summary>
-public sealed partial class OpenUplinkImplantEvent : InstantActionEvent
-{
-
-}
+/// <remarks>
+/// TODO: Delete this and just add a ToggleUIOnTriggerComponent
+/// </remarks>
+public sealed partial class OpenUplinkImplantEvent : InstantActionEvent;
diff --git a/Content.Shared/Implants/ReplacementImplantSystem.cs b/Content.Shared/Implants/ReplacementImplantSystem.cs
new file mode 100644 (file)
index 0000000..b206091
--- /dev/null
@@ -0,0 +1,34 @@
+using Content.Shared.Implants.Components;
+using Content.Shared.Whitelist;
+using Robust.Shared.Containers;
+
+namespace Content.Shared.Implants;
+
+public sealed class ReplacementImplantSystem : EntitySystem
+{
+    [Dependency] private readonly SharedContainerSystem _container = default!;
+    [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<ReplacementImplantComponent, ImplantImplantedEvent>(OnImplantImplanted);
+    }
+
+    private void OnImplantImplanted(Entity<ReplacementImplantComponent> ent, ref ImplantImplantedEvent args)
+    {
+        if (!_container.TryGetContainer(args.Implanted, ImplanterComponent.ImplantSlotId, out var implantContainer))
+            return;
+
+        foreach (var implant in implantContainer.ContainedEntities)
+        {
+            if (implant == ent.Owner)
+                continue; // don't delete the replacement
+
+            if (_whitelist.IsWhitelistPass(ent.Comp.Whitelist, implant))
+                PredictedQueueDel(implant);
+        }
+
+    }
+}
index 6e806384a0a69fc225ff929b1e47da15c83fe221..6ff6d42d3baa50c7fff957a3894a1f09f504d38a 100644 (file)
@@ -118,7 +118,7 @@ public abstract class SharedImplanterSystem : EntitySystem
     //Set to draw mode if not implant only
     public void Implant(EntityUid user, EntityUid target, EntityUid implanter, ImplanterComponent component)
     {
-        if (!CanImplant(user, target, implanter, component, out var implant, out var implantComp))
+        if (!CanImplant(user, target, implanter, component, out var implant, out _))
             return;
 
         // Check if we are trying to implant a implant which is already implanted
@@ -137,7 +137,6 @@ public abstract class SharedImplanterSystem : EntitySystem
 
         if (component.ImplanterSlot.ContainerSlot != null)
             _container.Remove(implant.Value, component.ImplanterSlot.ContainerSlot);
-        implantComp.ImplantedEntity = target;
         implantContainer.OccludesLight = false;
         _container.Insert(implant.Value, implantContainer);
 
@@ -280,7 +279,6 @@ public abstract class SharedImplanterSystem : EntitySystem
     private void DrawImplantIntoImplanter(EntityUid implanter, EntityUid target, EntityUid implant, BaseContainer implantContainer, ContainerSlot implanterContainer, SubdermalImplantComponent implantComp)
     {
         _container.Remove(implant, implantContainer);
-        implantComp.ImplantedEntity = null;
         _container.Insert(implant, implanterContainer);
 
         var ev = new TransferDnaEvent { Donor = target, Recipient = implanter };
diff --git a/Content.Shared/Implants/SharedSubdermalImplantSystem.Relays.cs b/Content.Shared/Implants/SharedSubdermalImplantSystem.Relays.cs
new file mode 100644 (file)
index 0000000..4c0b2c2
--- /dev/null
@@ -0,0 +1,50 @@
+using Content.Shared.Implants.Components;
+using Content.Shared.Interaction;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Mobs;
+
+namespace Content.Shared.Implants;
+
+public abstract partial class SharedSubdermalImplantSystem
+{
+    public void InitializeRelay()
+    {
+        SubscribeLocalEvent<ImplantedComponent, MobStateChangedEvent>(RelayToImplantEvent);
+        SubscribeLocalEvent<ImplantedComponent, AfterInteractUsingEvent>(RelayToImplantEvent);
+        SubscribeLocalEvent<ImplantedComponent, SuicideEvent>(RelayToImplantEvent);
+    }
+
+    /// <summary>
+    /// Relays events from the implanted to the implant.
+    /// </summary>
+    private void RelayToImplantEvent<T>(EntityUid uid, ImplantedComponent component, T args) where T : notnull
+    {
+        if (!_container.TryGetContainer(uid, ImplanterComponent.ImplantSlotId, out var implantContainer))
+            return;
+
+        var relayEv = new ImplantRelayEvent<T>(args, uid);
+        foreach (var implant in implantContainer.ContainedEntities)
+        {
+            if (args is HandledEntityEventArgs { Handled: true })
+                return;
+
+            RaiseLocalEvent(implant, relayEv);
+        }
+    }
+}
+
+/// <summary>
+/// Wrapper for relaying events from an implanted entity to their implants.
+/// </summary>
+public sealed class ImplantRelayEvent<T> where T : notnull
+{
+    public readonly T Event;
+
+    public readonly EntityUid ImplantedEntity;
+
+    public ImplantRelayEvent(T ev, EntityUid implantedEntity)
+    {
+        Event = ev;
+        ImplantedEntity = implantedEntity;
+    }
+}
index 4c015f1209cad0b1a0cc3875f56419f972e7b47d..630416b598c6ec5315818cc8798e17cd00000f01 100644 (file)
@@ -1,90 +1,76 @@
-using System.Linq;
 using Content.Shared.Actions;
 using Content.Shared.Implants.Components;
-using Content.Shared.Interaction;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Mobs;
-using Content.Shared.Tag;
-using JetBrains.Annotations;
 using Robust.Shared.Containers;
+using Robust.Shared.Network;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
 
 namespace Content.Shared.Implants;
 
-public abstract class SharedSubdermalImplantSystem : EntitySystem
+public abstract partial class SharedSubdermalImplantSystem : EntitySystem
 {
-    [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
+    [Dependency] private readonly SharedActionsSystem _actions = default!;
     [Dependency] private readonly SharedContainerSystem _container = default!;
-    [Dependency] private readonly TagSystem _tag = default!;
-    [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
-
-    public const string BaseStorageId = "storagebase";
-
-    private static readonly ProtoId<TagPrototype> MicroBombTag = "MicroBomb";
-    private static readonly ProtoId<TagPrototype> MacroBombTag = "MacroBomb";
+    [Dependency] private readonly INetManager _net = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
 
     public override void Initialize()
     {
+        base.Initialize();
+        InitializeRelay();
+
         SubscribeLocalEvent<SubdermalImplantComponent, EntGotInsertedIntoContainerMessage>(OnInsert);
         SubscribeLocalEvent<SubdermalImplantComponent, ContainerGettingRemovedAttemptEvent>(OnRemoveAttempt);
         SubscribeLocalEvent<SubdermalImplantComponent, EntGotRemovedFromContainerMessage>(OnRemove);
-
-        SubscribeLocalEvent<ImplantedComponent, MobStateChangedEvent>(RelayToImplantEvent);
-        SubscribeLocalEvent<ImplantedComponent, AfterInteractUsingEvent>(RelayToImplantEvent);
-        SubscribeLocalEvent<ImplantedComponent, SuicideEvent>(RelayToImplantEvent);
     }
 
-    private void OnInsert(EntityUid uid, SubdermalImplantComponent component, EntGotInsertedIntoContainerMessage args)
+    private void OnInsert(Entity<SubdermalImplantComponent> ent, ref EntGotInsertedIntoContainerMessage args)
     {
-        if (component.ImplantedEntity == null)
+        // The results of the container change are already networked on their own
+        if (_timing.ApplyingState)
             return;
 
-        if (!string.IsNullOrWhiteSpace(component.ImplantAction))
-        {
-            _actionsSystem.AddAction(component.ImplantedEntity.Value, ref component.Action, component.ImplantAction, uid);
-        }
+        if (args.Container.ID != ImplanterComponent.ImplantSlotId)
+            return;
 
-        // replace micro bomb with macro bomb
-        // TODO: this shouldn't be hardcoded here
-        if (_container.TryGetContainer(component.ImplantedEntity.Value, ImplanterComponent.ImplantSlotId, out var implantContainer) && _tag.HasTag(uid, MacroBombTag))
-        {
-            foreach (var implant in implantContainer.ContainedEntities)
-            {
-                if (_tag.HasTag(implant, MicroBombTag))
-                {
-                    _container.Remove(implant, implantContainer);
-                    PredictedQueueDel(implant);
-                }
-            }
-        }
+        ent.Comp.ImplantedEntity = args.Container.Owner;
+        Dirty(ent);
 
-        var ev = new ImplantImplantedEvent(uid, component.ImplantedEntity.Value);
-        RaiseLocalEvent(uid, ref ev);
+        EntityManager.AddComponents(ent.Comp.ImplantedEntity.Value, ent.Comp.ImplantComponents);
+        if (ent.Comp.ImplantAction != null)
+            _actions.AddAction(ent.Comp.ImplantedEntity.Value, ref ent.Comp.Action, ent.Comp.ImplantAction, ent.Owner);
+
+        var ev = new ImplantImplantedEvent(ent.Owner, ent.Comp.ImplantedEntity.Value);
+        RaiseLocalEvent(ent.Owner, ref ev);
     }
 
-    private void OnRemoveAttempt(EntityUid uid, SubdermalImplantComponent component, ContainerGettingRemovedAttemptEvent args)
+    private void OnRemoveAttempt(Entity<SubdermalImplantComponent> ent, ref ContainerGettingRemovedAttemptEvent args)
     {
-        if (component.Permanent && component.ImplantedEntity != null)
+        if (ent.Comp.Permanent && ent.Comp.ImplantedEntity != null)
             args.Cancel();
     }
 
-    private void OnRemove(EntityUid uid, SubdermalImplantComponent component, EntGotRemovedFromContainerMessage args)
+    private void OnRemove(Entity<SubdermalImplantComponent> ent, ref EntGotRemovedFromContainerMessage args)
     {
-        if (component.ImplantedEntity == null || Terminating(component.ImplantedEntity.Value))
+        // The results of the container change are already networked on their own
+        if (_timing.ApplyingState)
             return;
 
-        if (component.ImplantAction != null)
-            _actionsSystem.RemoveProvidedActions(component.ImplantedEntity.Value, uid);
+        if (args.Container.ID != ImplanterComponent.ImplantSlotId)
+            return;
 
-        if (!_container.TryGetContainer(uid, BaseStorageId, out var storageImplant))
+        if (ent.Comp.ImplantedEntity == null || Terminating(ent.Comp.ImplantedEntity.Value))
             return;
 
-        var containedEntites = storageImplant.ContainedEntities.ToArray();
+        EntityManager.RemoveComponents(ent.Comp.ImplantedEntity.Value, ent.Comp.ImplantComponents);
+        _actions.RemoveAction(ent.Comp.ImplantedEntity.Value, ent.Comp.Action);
+        ent.Comp.Action = null;
 
-        foreach (var entity in containedEntites)
-        {
-            _transformSystem.DropNextTo(entity, uid);
-        }
+        var ev = new ImplantRemovedEvent(ent.Owner, ent.Comp.ImplantedEntity.Value);
+        RaiseLocalEvent(ent.Owner, ref ev);
+
+        ent.Comp.ImplantedEntity = null;
+        Dirty(ent);
     }
 
     /// <summary>
@@ -106,23 +92,26 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
     /// <returns>
     /// The implant, if it was successfully created. Otherwise, null.
     /// </returns>>
-    public EntityUid? AddImplant(EntityUid uid, String implantId)
+    public EntityUid? AddImplant(EntityUid target, EntProtoId implantId)
     {
-        var coords = Transform(uid).Coordinates;
-        var ent = Spawn(implantId, coords);
+        if (_net.IsClient)
+            return null; // can't interact with predicted spawns yet
 
-        if (TryComp<SubdermalImplantComponent>(ent, out var implant))
+        var coords = Transform(target).Coordinates;
+        var implant = Spawn(implantId, coords);
+
+        if (TryComp<SubdermalImplantComponent>(implant, out var implantComp))
         {
-            ForceImplant(uid, ent, implant);
+            ForceImplant(target, (implant, implantComp));
         }
         else
         {
-            Log.Warning($"Found invalid starting implant '{implantId}' on {uid} {ToPrettyString(uid):implanted}");
-            Del(ent);
+            Log.Warning($"Tried to inject implant '{implantId}' without SubdermalImplantComponent into {ToPrettyString(target):implanted}");
+            Del(implant);
             return null;
         }
 
-        return ent;
+        return implant;
     }
 
     /// <summary>
@@ -131,15 +120,16 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
     /// </summary>
     /// <param name="target">The entity to be implanted</param>
     /// <param name="implant"> The implant</param>
-    /// <param name="component">The implant component</param>
-    public void ForceImplant(EntityUid target, EntityUid implant, SubdermalImplantComponent component)
+    public void ForceImplant(EntityUid target, Entity<SubdermalImplantComponent?> implant)
     {
+        if (!Resolve(implant, ref implant.Comp))
+            return;
+
         //If the target doesn't have the implanted component, add it.
         var implantedComp = EnsureComp<ImplantedComponent>(target);
-        var implantContainer = implantedComp.ImplantContainer;
 
-        component.ImplantedEntity = target;
-        _container.Insert(implant, implantContainer);
+        implant.Comp.ImplantedEntity = target;
+        _container.Insert(implant.Owner, implantedComp.ImplantContainer);
     }
 
     /// <summary>
@@ -147,77 +137,60 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
     /// </summary>
     /// <param name="target">the implanted entity</param>
     /// <param name="implant">the implant</param>
-    [PublicAPI]
-    public void ForceRemove(EntityUid target, EntityUid implant)
+    public void ForceRemove(Entity<ImplantedComponent?> target, EntityUid implant)
     {
-        if (!TryComp<ImplantedComponent>(target, out var implanted))
+        if (!Resolve(target, ref target.Comp))
             return;
 
-        var implantContainer = implanted.ImplantContainer;
-
-        _container.Remove(implant, implantContainer);
-        QueueDel(implant);
+        _container.Remove(implant, target.Comp.ImplantContainer);
+        PredictedQueueDel(implant);
     }
 
     /// <summary>
     /// Removes and deletes implants by force
     /// </summary>
     /// <param name="target">The entity to have implants removed</param>
-    [PublicAPI]
-    public void WipeImplants(EntityUid target)
-    {
-        if (!TryComp<ImplantedComponent>(target, out var implanted))
-            return;
-
-        var implantContainer = implanted.ImplantContainer;
-
-        _container.CleanContainer(implantContainer);
-    }
-
-    //Relays from the implanted to the implant
-    private void RelayToImplantEvent<T>(EntityUid uid, ImplantedComponent component, T args) where T : notnull
+    public void WipeImplants(Entity<ImplantedComponent?> target)
     {
-        if (!_container.TryGetContainer(uid, ImplanterComponent.ImplantSlotId, out var implantContainer))
+        if (!Resolve(target, ref target.Comp, false))
             return;
 
-        var relayEv = new ImplantRelayEvent<T>(args, uid);
-        foreach (var implant in implantContainer.ContainedEntities)
-        {
-            if (args is HandledEntityEventArgs { Handled : true })
-                return;
-
-            RaiseLocalEvent(implant, relayEv);
-        }
+        _container.CleanContainer(target.Comp.ImplantContainer);
     }
 }
 
-public sealed class ImplantRelayEvent<T> where T : notnull
+/// <summary>
+/// Event that is raised whenever someone is implanted with any given implant.
+/// Raised on the the implant entity.
+/// </summary>
+/// <remarks>
+/// implant implant implant implant
+/// </remarks>
+[ByRefEvent]
+public readonly record struct ImplantImplantedEvent
 {
-    public readonly T Event;
-
-    public readonly EntityUid ImplantedEntity;
+    public readonly EntityUid Implant;
+    public readonly EntityUid Implanted;
 
-    public ImplantRelayEvent(T ev, EntityUid implantedEntity)
+    public ImplantImplantedEvent(EntityUid implant, EntityUid implanted)
     {
-        Event = ev;
-        ImplantedEntity = implantedEntity;
+        Implant = implant;
+        Implanted = implanted;
     }
 }
 
 /// <summary>
-/// Event that is raised whenever someone is implanted with any given implant.
+/// Event that is raised whenever an implant is removed from someone.
 /// Raised on the the implant entity.
 /// </summary>
-/// <remarks>
-/// implant implant implant implant
-/// </remarks>
+
 [ByRefEvent]
-public readonly struct ImplantImplantedEvent
+public readonly record struct ImplantRemovedEvent
 {
     public readonly EntityUid Implant;
-    public readonly EntityUid? Implanted;
+    public readonly EntityUid Implanted;
 
-    public ImplantImplantedEvent(EntityUid implant, EntityUid? implanted)
+    public ImplantRemovedEvent(EntityUid implant, EntityUid implanted)
     {
         Implant = implant;
         Implanted = implanted;
diff --git a/Content.Shared/Implants/StorageImplantSystem.cs b/Content.Shared/Implants/StorageImplantSystem.cs
new file mode 100644 (file)
index 0000000..748f9f3
--- /dev/null
@@ -0,0 +1,36 @@
+using System.Linq;
+using Content.Shared.Implants.Components;
+using Content.Shared.Storage;
+using Robust.Shared.Containers;
+using Robust.Shared.Network;
+
+namespace Content.Shared.Implants;
+
+public sealed class StorageImplantSystem : EntitySystem
+{
+    [Dependency] private readonly SharedContainerSystem _container = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly INetManager _net = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<StorageImplantComponent, ImplantRemovedEvent>(OnImplantRemoved);
+    }
+
+    private void OnImplantRemoved(Entity<StorageImplantComponent> ent, ref ImplantRemovedEvent args)
+    {
+        if (_net.IsClient)
+            return; // TODO: RandomPredicted and DropNextToPredicted
+
+        if (!_container.TryGetContainer(ent.Owner, StorageComponent.ContainerId, out var storageImplant))
+            return;
+
+        var contained = storageImplant.ContainedEntities.ToArray();
+        foreach (var entity in contained)
+        {
+            _transform.DropNextTo(entity, ent.Owner);
+        }
+    }
+}
similarity index 94%
rename from Content.Shared/Mindshield/FakeMindShield/SharedFakeMindshieldSystem.cs
rename to Content.Shared/Mindshield/FakeMindShield/FakeMindshieldSystem.cs
index c82f2b286393563c61cb5dea5f41da90240ae9b0..4d7d457321f2ea710827dc079b4848d4c4f8a692 100644 (file)
@@ -8,7 +8,7 @@ using Robust.Shared.Timing;
 
 namespace Content.Shared.Mindshield.FakeMindShield;
 
-public sealed class SharedFakeMindShieldSystem : EntitySystem
+public sealed class FakeMindShieldSystem : EntitySystem
 {
     [Dependency] private readonly SharedActionsSystem _actions = default!;
     [Dependency] private readonly TagSystem _tag = default!;
@@ -24,9 +24,11 @@ public sealed class SharedFakeMindShieldSystem : EntitySystem
         SubscribeLocalEvent<FakeMindShieldComponent, ChameleonControllerOutfitSelectedEvent>(OnChameleonControllerOutfitSelected);
     }
 
-    private void OnToggleMindshield(EntityUid uid, FakeMindShieldComponent comp, FakeMindShieldToggleEvent toggleEvent)
+    private void OnToggleMindshield(EntityUid uid, FakeMindShieldComponent comp, FakeMindShieldToggleEvent args)
     {
         comp.IsEnabled = !comp.IsEnabled;
+        args.Toggle = true;
+        args.Handled = true;
         Dirty(uid, comp);
     }
 
diff --git a/Content.Shared/Mindshield/FakeMindShield/SharedFakeMindShieldImplantSystem.cs b/Content.Shared/Mindshield/FakeMindShield/SharedFakeMindShieldImplantSystem.cs
deleted file mode 100644 (file)
index a597e03..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-using Content.Shared.Actions;
-using Content.Shared.Implants;
-using Content.Shared.Implants.Components;
-using Content.Shared.Mindshield.Components;
-using Robust.Shared.Containers;
-
-namespace Content.Shared.Mindshield.FakeMindShield;
-
-public sealed class SharedFakeMindShieldImplantSystem : EntitySystem
-{
-    [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
-    public override void Initialize()
-    {
-        base.Initialize();
-        SubscribeLocalEvent<SubdermalImplantComponent, FakeMindShieldToggleEvent>(OnFakeMindShieldToggle);
-        SubscribeLocalEvent<FakeMindShieldImplantComponent, ImplantImplantedEvent>(ImplantCheck);
-        SubscribeLocalEvent<FakeMindShieldImplantComponent, EntGotRemovedFromContainerMessage>(ImplantDraw);
-    }
-
-    /// <summary>
-    /// Raise the Action of a Implanted user toggling their implant to the FakeMindshieldComponent on their entity
-    /// </summary>
-    private void OnFakeMindShieldToggle(Entity<SubdermalImplantComponent> entity, ref FakeMindShieldToggleEvent ev)
-    {
-        ev.Handled = true;
-        if (entity.Comp.ImplantedEntity is not { } ent)
-            return;
-
-        if (!TryComp<FakeMindShieldComponent>(ent, out var comp))
-            return;
-        // TODO: is there a reason this cant set ev.Toggle = true;
-        _actionsSystem.SetToggled((ev.Action, ev.Action), !comp.IsEnabled); // Set it to what the Mindshield component WILL be after this
-        RaiseLocalEvent(ent, ev); //this reraises the action event to support an eventual future Changeling Antag which will also be using this component for it's "mindshield" ability
-    }
-    private void ImplantCheck(EntityUid uid, FakeMindShieldImplantComponent component ,ref ImplantImplantedEvent ev)
-    {
-        if (ev.Implanted != null)
-            EnsureComp<FakeMindShieldComponent>(ev.Implanted.Value);
-    }
-
-    private void ImplantDraw(Entity<FakeMindShieldImplantComponent> ent, ref EntGotRemovedFromContainerMessage ev)
-    {
-        RemComp<FakeMindShieldComponent>(ev.Container.Owner);
-    }
-}
index 97435c229de584d7442862304170bd209263ddca..c8ea03502ab9f8105bc12632be8f9fe73bbf5c19 100644 (file)
     iconOn: { sprite: Interface/Actions/actions_fakemindshield.rsi, state: icon-on }
     itemIconStyle: NoItem
     useDelay: 1
+    raiseOnUser: true
   - type: InstantAction
     event: !type:FakeMindShieldToggleEvent
   - type: Tag
index e1918ef5e604b6c2525d9fae934517599de4219b..316acba6fc66991a58a5ec101886bf349aa69844 100644 (file)
@@ -6,89 +6,89 @@
   parent: BaseItem
   abstract: true
   components:
-    - type: ItemSlots
-    - type: ContainerContainer
-      containers:
-        implanter_slot: !type:ContainerSlot { }
-    - type: Implanter
+  - type: ItemSlots
+  - type: ContainerContainer
+    containers:
+      implanter_slot: !type:ContainerSlot { }
+  - type: Implanter
+    whitelist:
+      components:
+      - MobState # no chair microbomb
+    blacklist:
+      components:
+      - Guardian # no holoparasite macrobomb wombo combo
+      tags:
+      - Unimplantable
+    currentMode: Draw
+    implanterSlot:
+      name: Implant
+      locked: True
+      priority: 0
       whitelist:
-        components:
-        - MobState # no chair microbomb
-      blacklist:
-        components:
-        - Guardian # no holoparasite macrobomb wombo combo
         tags:
-        - Unimplantable
-      currentMode: Draw
-      implanterSlot:
-        name: Implant
-        locked: True
-        priority: 0
-        whitelist:
-          tags:
-            - SubdermalImplant
-      allowDeimplantAll: false
-      deimplantWhitelist:
-      - SadTromboneImplant
-      - LightImplant
-      - BikeHornImplant
-      - TrackingImplant
-      - StorageImplant
-      - FreedomImplant
-      - UplinkImplant
-      - EmpImplant
-      - ScramImplant
-      - DnaScramblerImplant
-      - MicroBombImplant
-      - MacroBombImplant
-      - DeathAcidifierImplant
-      - DeathRattleImplant
-      - MindShieldImplant
-      - FakeMindShieldImplant
-      - RadioImplant
-      - ChameleonControllerImplant
-      deimplantFailureDamage:
-        types:
-          Cellular: 50
-          Heat: 10
-    - type: Sprite
-      sprite: Objects/Specific/Medical/implanter.rsi
-      state: implanter0
-      layers:
-        - state: implanter0
-          map: [ "implantOnly" ]
-          visible: true
-        - state: implanter1
-          map: [ "implantFull" ]
-          visible: false
-    - type: Item
-      sprite: Objects/Specific/Medical/implanter.rsi
-      heldPrefix: implanter
-      size: Small
-    - type: Appearance
-    - type: GenericVisualizer
-      visuals:
-        enum.ImplanterVisuals.Full:
-          implantFull:
-            True: {visible: true}
-            False: {visible: false}
-        enum.ImplanterImplantOnlyVisuals.ImplantOnly:
-          implantOnly:
-            True: {state: broken}
-            False: {state: implanter0}
-    - type: UserInterface
-      interfaces:
-        enum.DeimplantUiKey.Key:
-          type: DeimplantBoundUserInterface
+        - SubdermalImplant
+    allowDeimplantAll: false
+    deimplantWhitelist:
+    - SadTromboneImplant
+    - LightImplant
+    - BikeHornImplant
+    - TrackingImplant
+    - StorageImplant
+    - FreedomImplant
+    - UplinkImplant
+    - EmpImplant
+    - ScramImplant
+    - DnaScramblerImplant
+    - MicroBombImplant
+    - MacroBombImplant
+    - DeathAcidifierImplant
+    - DeathRattleImplant
+    - MindShieldImplant
+    - FakeMindShieldImplant
+    - RadioImplant
+    - ChameleonControllerImplant
+    deimplantFailureDamage:
+      types:
+        Cellular: 50
+        Heat: 10
+  - type: Sprite
+    sprite: Objects/Specific/Medical/implanter.rsi
+    state: implanter0
+    layers:
+    - state: implanter0
+      map: [ "implantOnly" ]
+      visible: true
+    - state: implanter1
+      map: [ "implantFull" ]
+      visible: false
+  - type: Item
+    sprite: Objects/Specific/Medical/implanter.rsi
+    heldPrefix: implanter
+    size: Small
+  - type: Appearance
+  - type: GenericVisualizer
+    visuals:
+      enum.ImplanterVisuals.Full:
+        implantFull:
+          True: {visible: true}
+          False: {visible: false}
+      enum.ImplanterImplantOnlyVisuals.ImplantOnly:
+        implantOnly:
+          True: {state: broken}
+          False: {state: implanter0}
+  - type: UserInterface
+    interfaces:
+      enum.DeimplantUiKey.Key:
+        type: DeimplantBoundUserInterface
 
 - type: entity
   id: Implanter
   parent: BaseImplanter
   description: A disposable syringe exclusively designed for the injection and extraction of subdermal implants.
   components:
-    - type: Tag
-      tags:
-        - Trash
+  - type: Tag
+    tags:
+    - Trash
 
 - type: entity
   parent: Implanter
   description: A disposable syringe exclusively designed for the injection of subdermal implants.
   abstract: true
   components:
-    - type: Sprite
-      sprite: Objects/Specific/Medical/implanter.rsi
-      state: implanter0
-      layers:
-        - state: implanter1
-          map: [ "implantFull" ]
-          visible: true
-        - state: implanter0
-          map: [ "implantOnly" ]
-    - type: Implanter
-      currentMode: Inject
-      implantOnly: true
+  - type: Sprite
+    sprite: Objects/Specific/Medical/implanter.rsi
+    state: implanter0
+    layers:
+    - state: implanter1
+      map: [ "implantFull" ]
+      visible: true
+    - state: implanter0
+      map: [ "implantOnly" ]
+  - type: Implanter
+    currentMode: Inject
+    implantOnly: true
 
 - type: entity
   id: BaseImplantOnlyImplanterSyndi
   description: A compact disposable syringe exclusively designed for the injection of subdermal implants. Make sure to scrub it with soap or a rag to remove residual DNA after use!
   abstract: true
   components:
-    - type: Item
-      sprite: Objects/Specific/Medical/syndi_implanter.rsi
-      heldPrefix: implanter
-    - type: Sprite
-      sprite: Objects/Specific/Medical/syndi_implanter.rsi
-      state: implanter1
-      layers:
-        - state: implanter0
-          map: [ "implantFull" ]
-          visible: true
-        - state: implanter1
-          map: [ "implantOnly" ]
-    - type: GenericVisualizer
-      visuals:
-        enum.ImplanterVisuals.Full:
-          implantFull:
-            True: {visible: true}
-            False: {visible: false}
-        enum.ImplanterImplantOnlyVisuals.ImplantOnly:
-          implantOnly:
-            True: {state: broken}
-            False: {state: implanter1}
-    - type: Tag
-      tags: []
+  - type: Item
+    sprite: Objects/Specific/Medical/syndi_implanter.rsi
+    heldPrefix: implanter
+  - type: Sprite
+    sprite: Objects/Specific/Medical/syndi_implanter.rsi
+    state: implanter1
+    layers:
+    - state: implanter0
+      map: [ "implantFull" ]
+      visible: true
+    - state: implanter1
+      map: [ "implantOnly" ]
+  - type: GenericVisualizer
+    visuals:
+      enum.ImplanterVisuals.Full:
+        implantFull:
+          True: {visible: true}
+          False: {visible: false}
+      enum.ImplanterImplantOnlyVisuals.ImplantOnly:
+        implantOnly:
+          True: {state: broken}
+          False: {state: implanter1}
+  - type: Tag
+    tags: []
 
 #Fun implanters
 
   name: sad trombone implanter
   parent: BaseImplantOnlyImplanter
   components:
-    - type: Implanter
-      implant: SadTromboneImplant
+  - type: Implanter
+    implant: SadTromboneImplant
 
 - type: entity
   id: LightImplanter
   name: light implanter
   parent: BaseImplantOnlyImplanter
   components:
-    - type: Implanter
-      implant: LightImplant
+  - type: Implanter
+    implant: LightImplant
 
 - type: entity
   id: BikeHornImplanter
   name: bike horn implanter
   parent: BaseImplantOnlyImplanter
   components:
-    - type: Implanter
-      implant: BikeHornImplant
+  - type: Implanter
+    implant: BikeHornImplant
 
 #Security implanters
 
   name: tracking implanter
   parent: BaseImplantOnlyImplanter
   components:
-    - type: Implanter
-      implant: TrackingImplant
+  - type: Implanter
+    implant: TrackingImplant
 
 #Traitor implanters
 
   name: storage implanter
   parent: BaseImplantOnlyImplanterSyndi
   components:
-    - type: Implanter
-      implant: StorageImplant
+  - type: Implanter
+    implant: StorageImplant
 
 - type: entity
   id: FreedomImplanter
   name: freedom implanter
   parent: BaseImplantOnlyImplanterSyndi
   components:
-    - type: Implanter
-      implant: FreedomImplant
+  - type: Implanter
+    implant: FreedomImplant
 
 - type: entity
   id: RadioImplanter
   name: EMP implanter
   parent: BaseImplantOnlyImplanterSyndi
   components:
-    - type: Implanter
-      implant: EmpImplant
+  - type: Implanter
+    implant: EmpImplant
 
 - type: entity
   id: ScramImplanter
   name: scram implanter
   parent: BaseImplantOnlyImplanterSyndi
   components:
-    - type: Implanter
-      implant: ScramImplant
+  - type: Implanter
+    implant: ScramImplant
 
 - type: entity
   id: DnaScramblerImplanter
   name: DNA scrambler implanter
   parent: BaseImplantOnlyImplanterSyndi
   components:
-    - type: Implanter
-      implant: DnaScramblerImplant
+  - type: Implanter
+    implant: DnaScramblerImplant
 
 - type: entity
   id: ChameleonControllerImplanter
   name: micro-bomb implanter
   parent: BaseImplantOnlyImplanterSyndi
   components:
-    - type: Implanter
-      implant: MicroBombImplant
+  - type: Implanter
+    implant: MicroBombImplant
 
 - type: entity
   id: MacroBombImplanter
   name: macro-bomb implanter
   parent: BaseImplantOnlyImplanterSyndi
   components:
-    - type: Implanter
-      implant: MacroBombImplant
+  - type: Implanter
+    implant: MacroBombImplant
 
 - type: entity
   id: DeathRattleImplanter
   name: death rattle implanter
   parent: BaseImplantOnlyImplanterSyndi
   components:
-    - type: Implanter
-      implant: DeathRattleImplant
+  - type: Implanter
+    implant: DeathRattleImplant
 
 - type: entity
   id: DeathAcidifierImplanter
   name: mindshield implanter
   parent: BaseImplantOnlyImplanter
   components:
-    - type: Implanter
-      implant: MindShieldImplant
+  - type: Implanter
+    implant: MindShieldImplant
 
 # Centcomm implanters
 
index 6a4ad2466454cbb9565f77182e3a1ee1103160aa..5ff8c79fb41dad6faeae7e4667affcfaf022ff4b 100644 (file)
   description: This implant plays a sad tune when the user dies.
   categories: [ HideSpawnMenu ]
   components:
-    - type: SubdermalImplant
-      whitelist:
-        components:
-        - MobState # admeme implanting a chair with trombone implant needs to give the chair mobstate so it can die first
-    - type: TriggerOnMobstateChange
-      mobState:
-      - Dead
-    - type: EmitSoundOnTrigger
-      sound:
-        collection: SadTrombone
-        params:
-          variation: 0.125
+  - type: SubdermalImplant
+    whitelist:
+      components:
+      - MobState # admeme implanting a chair with trombone implant needs to give the chair mobstate so it can die first
+  - type: TriggerOnMobstateChange
+    mobState:
+    - Dead
+  - type: EmitSoundOnTrigger
+    sound:
+      collection: SadTrombone
+      params:
+        variation: 0.125
 
 - type: entity
   parent: BaseSubdermalImplant
   description: This implant emits light from the user's skin on activation.
   categories: [ HideSpawnMenu ]
   components:
-    - type: SubdermalImplant
-      implantAction: ActionToggleLight
-    - type: PointLight
-      enabled: false
-      radius: 2.5
-      softness: 5
-      mask: /Textures/Effects/LightMasks/cone.png
-      autoRot: true
-    - type: Tag
-      tags:
-        - SubdermalImplant
-        - HideContextMenu
-        - Flashlight
-    - type: UnpoweredFlashlight
+  - type: SubdermalImplant
+    implantAction: ActionToggleLight
+  - type: PointLight
+    enabled: false
+    radius: 2.5
+    softness: 5
+    mask: /Textures/Effects/LightMasks/cone.png
+    autoRot: true
+  - type: Tag
+    tags:
+    - SubdermalImplant
+    - HideContextMenu
+    - Flashlight
+  - type: UnpoweredFlashlight
 
 - type: entity
   parent: BaseSubdermalImplant
   description: This implant grants hidden storage within a person's body using bluespace technology.
   categories: [ HideSpawnMenu ]
   components:
+    - type: StorageImplant
     - type: SubdermalImplant
       implantAction: ActionOpenStorageImplant
       whitelist:
   components:
     - type: SubdermalImplant
       permanent: true
+    - type: ReplacementImplant
+      whitelist:
+        tags:
+        - MicroBomb # replace microbomb implant with macrobomb
     - type: TriggerOnMobstateChange #activates the timer
       mobState:
       - Dead
       canCreateVacuum: true
     - type: Tag
       tags:
-        - SubdermalImplant
-        - HideContextMenu
-        - MacroBomb
+      - SubdermalImplant
+      - HideContextMenu
+      - MacroBomb
 
 - type: entity
   parent: BaseSubdermalImplant
   description: This implant allows the implanter to produce a fake signal that NT security huds use to identify individuals implanted with a mindshield.
   categories: [ HideSpawnMenu ]
   components:
-      - type: SubdermalImplant
-        implantAction: FakeMindShieldToggleAction
-      - type: FakeMindShieldImplant
+  - type: SubdermalImplant
+    implantAction: FakeMindShieldToggleAction
+    implantComponents:
+    - type: FakeMindShield # TODO: put the component on the implant and use implant relay events for the status icon
+  - type: FakeMindShieldImplant
 
 # Sec and Command implants