diff --git a/Content.Client/Implants/ImplanterSystem.cs b/Content.Client/Implants/ImplanterSystem.cs index cca09f5dad..4ba4d015ca 100644 --- a/Content.Client/Implants/ImplanterSystem.cs +++ b/Content.Client/Implants/ImplanterSystem.cs @@ -23,6 +23,8 @@ public sealed class ImplanterSystem : SharedImplanterSystem { if (_uiSystem.TryGetOpenUi(uid, DeimplantUiKey.Key, out var bui)) { + // TODO: Don't use protoId for deimplanting + // and especially not raw strings! Dictionary 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 index 0000000000..5d814eddf8 --- /dev/null +++ b/Content.Client/Implants/SubdermalImplantSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared.Implants; + +namespace Content.Client.Implants; + +public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem; diff --git a/Content.Server/Implants/ChameleonControllerSystem.cs b/Content.Server/Implants/ChameleonControllerSystem.cs index 9e876f9399..930f2e3156 100644 --- a/Content.Server/Implants/ChameleonControllerSystem.cs +++ b/Content.Server/Implants/ChameleonControllerSystem.cs @@ -29,17 +29,17 @@ public sealed class ChameleonControllerSystem : SharedChameleonControllerSystem { base.Initialize(); - SubscribeLocalEvent(OnSelected); + SubscribeLocalEvent(OnSelected); SubscribeLocalEvent>(ChameleonControllerOutfitItemSelected); } - private void OnSelected(Entity ent, ref ChameleonControllerSelectedOutfitMessage args) + private void OnSelected(Entity ent, ref ChameleonControllerSelectedOutfitMessage args) { - if (!_delay.TryResetDelay(ent.Owner, true) || ent.Comp.ImplantedEntity == null || !HasComp(ent)) + if (!TryComp(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); } /// diff --git a/Content.Server/Implants/ImplanterSystem.cs b/Content.Server/Implants/ImplanterSystem.cs index 5023b1b3e4..48e83e4878 100644 --- a/Content.Server/Implants/ImplanterSystem.cs +++ b/Content.Server/Implants/ImplanterSystem.cs @@ -27,6 +27,7 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem SubscribeLocalEvent(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) diff --git a/Content.Server/Implants/RadioImplantSystem.cs b/Content.Server/Implants/RadioImplantSystem.cs index d9ba229057..c5ae1ce494 100644 --- a/Content.Server/Implants/RadioImplantSystem.cs +++ b/Content.Server/Implants/RadioImplantSystem.cs @@ -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(OnImplantImplanted); - SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnImplantRemoved); } /// @@ -20,19 +19,16 @@ public sealed class RadioImplantSystem : EntitySystem /// private void OnImplantImplanted(Entity ent, ref ImplantImplantedEvent args) { - if (args.Implanted == null) - return; - - var activeRadio = EnsureComp(args.Implanted.Value); + var activeRadio = EnsureComp(args.Implanted); foreach (var channel in ent.Comp.RadioChannels) { if (activeRadio.Channels.Add(channel)) ent.Comp.ActiveAddedChannels.Add(channel); } - EnsureComp(args.Implanted.Value); + EnsureComp(args.Implanted); - var intrinsicRadioTransmitter = EnsureComp(args.Implanted.Value); + var intrinsicRadioTransmitter = EnsureComp(args.Implanted); foreach (var channel in ent.Comp.RadioChannels) { if (intrinsicRadioTransmitter.Channels.Add(channel)) @@ -43,9 +39,9 @@ public sealed class RadioImplantSystem : EntitySystem /// /// Removes intrinsic radio components once the Radio Implant is removed /// - private void OnRemove(Entity ent, ref EntGotRemovedFromContainerMessage args) + private void OnImplantRemoved(Entity ent, ref ImplantRemovedEvent args) { - if (TryComp(args.Container.Owner, out var activeRadioComponent)) + if (TryComp(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(args.Container.Owner); + RemCompDeferred(args.Implanted); } } - if (!TryComp(args.Container.Owner, out var radioTransmitterComponent)) + if (!TryComp(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(args.Container.Owner); + RemCompDeferred(args.Implanted); } } } diff --git a/Content.Server/Implants/SubdermalImplantSystem.cs b/Content.Server/Implants/SubdermalImplantSystem.cs index f0530358a6..582b9cb2ac 100644 --- a/Content.Server/Implants/SubdermalImplantSystem.cs +++ b/Content.Server/Implants/SubdermalImplantSystem.cs @@ -18,6 +18,7 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem SubscribeLocalEvent>(OnStoreRelay); } + // TODO: This shouldn't be in the SubdermalImplantSystem private void OnStoreRelay(EntityUid uid, StoreComponent store, ImplantRelayEvent implantRelay) { var args = implantRelay.Event; diff --git a/Content.Server/Mindshield/MindShieldSystem.cs b/Content.Server/Mindshield/MindShieldSystem.cs index c04fb12027..bc5b65159b 100644 --- a/Content.Server/Mindshield/MindShieldSystem.cs +++ b/Content.Server/Mindshield/MindShieldSystem.cs @@ -27,7 +27,7 @@ public sealed class MindShieldSystem : EntitySystem base.Initialize(); SubscribeLocalEvent(OnImplantImplanted); - SubscribeLocalEvent(OnImplantDraw); + SubscribeLocalEvent(OnImplantRemoved); } private void OnImplantImplanted(Entity ent, ref ImplantImplantedEvent ev) @@ -35,8 +35,8 @@ public sealed class MindShieldSystem : EntitySystem if (ev.Implanted == null) return; - EnsureComp(ev.Implanted.Value); - MindShieldRemovalCheck(ev.Implanted.Value, ev.Implant); + EnsureComp(ev.Implanted); + MindShieldRemovalCheck(ev.Implanted, ev.Implant); } /// @@ -58,9 +58,9 @@ public sealed class MindShieldSystem : EntitySystem } } - private void OnImplantDraw(Entity ent, ref EntGotRemovedFromContainerMessage args) + private void OnImplantRemoved(Entity ent, ref ImplantRemovedEvent args) { - RemComp(args.Container.Owner); + RemComp(args.Implanted); } } diff --git a/Content.Shared/Implants/Components/ReplacementImplantComponent.cs b/Content.Shared/Implants/Components/ReplacementImplantComponent.cs new file mode 100644 index 0000000000..73a6d4d8b4 --- /dev/null +++ b/Content.Shared/Implants/Components/ReplacementImplantComponent.cs @@ -0,0 +1,18 @@ +using Content.Shared.Whitelist; +using Robust.Shared.GameStates; + +namespace Content.Shared.Implants.Components; + +/// +/// Added to implants with the see . +/// When implanted it will cause other implants in the whitelist to be deleted and thus replaced. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class ReplacementImplantComponent : Component +{ + /// + /// Whitelist for which implants to delete. + /// + [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 index 0000000000..289f01bfce --- /dev/null +++ b/Content.Shared/Implants/Components/StorageImplantComponent.cs @@ -0,0 +1,11 @@ +using Content.Shared.Storage; +using Robust.Shared.GameStates; + +namespace Content.Shared.Implants.Components; + +/// +/// Handles emptying the implant's when the implant is removed. +/// Without this the contents would be deleted. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class StorageImplantComponent : Component; diff --git a/Content.Shared/Implants/Components/SubdermalImplantComponent.cs b/Content.Shared/Implants/Components/SubdermalImplantComponent.cs index 390d113dfb..85f2672603 100644 --- a/Content.Shared/Implants/Components/SubdermalImplantComponent.cs +++ b/Content.Shared/Implants/Components/SubdermalImplantComponent.cs @@ -16,13 +16,21 @@ public sealed partial class SubdermalImplantComponent : Component /// /// Used where you want the implant to grant the owner an instant action. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("implantAction")] + [DataField] public EntProtoId? ImplantAction; + /// + /// The provided action entity. + /// [DataField, AutoNetworkedField] public EntityUid? Action; + /// + /// Components to add/remove to the implantee when the implant is injected/extracted. + /// + [DataField] + public ComponentRegistry ImplantComponents = new(); + /// /// The entity this implant is inside /// @@ -32,8 +40,7 @@ public sealed partial class SubdermalImplantComponent : Component /// /// Should this implant be removeable? /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("permanent"), AutoNetworkedField] + [DataField, AutoNetworkedField] public bool Permanent = false; /// @@ -61,23 +68,20 @@ public sealed partial class SubdermalImplantComponent : Component /// /// Used for opening the storage implant via action. /// -public sealed partial class OpenStorageImplantEvent : InstantActionEvent -{ - -} +/// +/// TODO: Delete this and just add a ToggleUIOnTriggerComponent +/// +public sealed partial class OpenStorageImplantEvent : InstantActionEvent; /// /// Used for triggering trigger events on the implant via action /// -public sealed partial class ActivateImplantEvent : InstantActionEvent -{ - -} +public sealed partial class ActivateImplantEvent : InstantActionEvent; /// /// Used for opening the uplink implant via action. /// -public sealed partial class OpenUplinkImplantEvent : InstantActionEvent -{ - -} +/// +/// TODO: Delete this and just add a ToggleUIOnTriggerComponent +/// +public sealed partial class OpenUplinkImplantEvent : InstantActionEvent; diff --git a/Content.Shared/Implants/ReplacementImplantSystem.cs b/Content.Shared/Implants/ReplacementImplantSystem.cs new file mode 100644 index 0000000000..b206091fe3 --- /dev/null +++ b/Content.Shared/Implants/ReplacementImplantSystem.cs @@ -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(OnImplantImplanted); + } + + private void OnImplantImplanted(Entity 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); + } + + } +} diff --git a/Content.Shared/Implants/SharedImplanterSystem.cs b/Content.Shared/Implants/SharedImplanterSystem.cs index 6e806384a0..6ff6d42d3b 100644 --- a/Content.Shared/Implants/SharedImplanterSystem.cs +++ b/Content.Shared/Implants/SharedImplanterSystem.cs @@ -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 index 0000000000..4c0b2c2361 --- /dev/null +++ b/Content.Shared/Implants/SharedSubdermalImplantSystem.Relays.cs @@ -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(RelayToImplantEvent); + SubscribeLocalEvent(RelayToImplantEvent); + SubscribeLocalEvent(RelayToImplantEvent); + } + + /// + /// Relays events from the implanted to the implant. + /// + private void RelayToImplantEvent(EntityUid uid, ImplantedComponent component, T args) where T : notnull + { + if (!_container.TryGetContainer(uid, ImplanterComponent.ImplantSlotId, out var implantContainer)) + return; + + var relayEv = new ImplantRelayEvent(args, uid); + foreach (var implant in implantContainer.ContainedEntities) + { + if (args is HandledEntityEventArgs { Handled: true }) + return; + + RaiseLocalEvent(implant, relayEv); + } + } +} + +/// +/// Wrapper for relaying events from an implanted entity to their implants. +/// +public sealed class ImplantRelayEvent where T : notnull +{ + public readonly T Event; + + public readonly EntityUid ImplantedEntity; + + public ImplantRelayEvent(T ev, EntityUid implantedEntity) + { + Event = ev; + ImplantedEntity = implantedEntity; + } +} diff --git a/Content.Shared/Implants/SharedSubdermalImplantSystem.cs b/Content.Shared/Implants/SharedSubdermalImplantSystem.cs index 4c015f1209..630416b598 100644 --- a/Content.Shared/Implants/SharedSubdermalImplantSystem.cs +++ b/Content.Shared/Implants/SharedSubdermalImplantSystem.cs @@ -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 MicroBombTag = "MicroBomb"; - private static readonly ProtoId MacroBombTag = "MacroBomb"; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IGameTiming _timing = default!; public override void Initialize() { + base.Initialize(); + InitializeRelay(); + SubscribeLocalEvent(OnInsert); SubscribeLocalEvent(OnRemoveAttempt); SubscribeLocalEvent(OnRemove); - - SubscribeLocalEvent(RelayToImplantEvent); - SubscribeLocalEvent(RelayToImplantEvent); - SubscribeLocalEvent(RelayToImplantEvent); } - private void OnInsert(EntityUid uid, SubdermalImplantComponent component, EntGotInsertedIntoContainerMessage args) + private void OnInsert(Entity 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 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 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 (!_container.TryGetContainer(uid, BaseStorageId, out var storageImplant)) + if (args.Container.ID != ImplanterComponent.ImplantSlotId) return; - var containedEntites = storageImplant.ContainedEntities.ToArray(); + if (ent.Comp.ImplantedEntity == null || Terminating(ent.Comp.ImplantedEntity.Value)) + return; - foreach (var entity in containedEntites) - { - _transformSystem.DropNextTo(entity, uid); - } + EntityManager.RemoveComponents(ent.Comp.ImplantedEntity.Value, ent.Comp.ImplantComponents); + _actions.RemoveAction(ent.Comp.ImplantedEntity.Value, ent.Comp.Action); + ent.Comp.Action = null; + + var ev = new ImplantRemovedEvent(ent.Owner, ent.Comp.ImplantedEntity.Value); + RaiseLocalEvent(ent.Owner, ref ev); + + ent.Comp.ImplantedEntity = null; + Dirty(ent); } /// @@ -106,23 +92,26 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem /// /// The implant, if it was successfully created. Otherwise, null. /// > - 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(ent, out var implant)) + var coords = Transform(target).Coordinates; + var implant = Spawn(implantId, coords); + + if (TryComp(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; } /// @@ -131,15 +120,16 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem /// /// The entity to be implanted /// The implant - /// The implant component - public void ForceImplant(EntityUid target, EntityUid implant, SubdermalImplantComponent component) + public void ForceImplant(EntityUid target, Entity implant) { + if (!Resolve(implant, ref implant.Comp)) + return; + //If the target doesn't have the implanted component, add it. var implantedComp = EnsureComp(target); - var implantContainer = implantedComp.ImplantContainer; - component.ImplantedEntity = target; - _container.Insert(implant, implantContainer); + implant.Comp.ImplantedEntity = target; + _container.Insert(implant.Owner, implantedComp.ImplantContainer); } /// @@ -147,60 +137,25 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem /// /// the implanted entity /// the implant - [PublicAPI] - public void ForceRemove(EntityUid target, EntityUid implant) + public void ForceRemove(Entity target, EntityUid implant) { - if (!TryComp(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); } /// /// Removes and deletes implants by force /// /// The entity to have implants removed - [PublicAPI] - public void WipeImplants(EntityUid target) + public void WipeImplants(Entity target) { - if (!TryComp(target, out var implanted)) + if (!Resolve(target, ref target.Comp, false)) return; - var implantContainer = implanted.ImplantContainer; - - _container.CleanContainer(implantContainer); - } - - //Relays from the implanted to the implant - private void RelayToImplantEvent(EntityUid uid, ImplantedComponent component, T args) where T : notnull - { - if (!_container.TryGetContainer(uid, ImplanterComponent.ImplantSlotId, out var implantContainer)) - return; - - var relayEv = new ImplantRelayEvent(args, uid); - foreach (var implant in implantContainer.ContainedEntities) - { - if (args is HandledEntityEventArgs { Handled : true }) - return; - - RaiseLocalEvent(implant, relayEv); - } - } -} - -public sealed class ImplantRelayEvent where T : notnull -{ - public readonly T Event; - - public readonly EntityUid ImplantedEntity; - - public ImplantRelayEvent(T ev, EntityUid implantedEntity) - { - Event = ev; - ImplantedEntity = implantedEntity; + _container.CleanContainer(target.Comp.ImplantContainer); } } @@ -212,12 +167,30 @@ public sealed class ImplantRelayEvent where T : notnull /// implant implant implant implant /// [ByRefEvent] -public readonly struct ImplantImplantedEvent +public readonly record struct ImplantImplantedEvent { public readonly EntityUid Implant; - public readonly EntityUid? Implanted; + public readonly EntityUid Implanted; - public ImplantImplantedEvent(EntityUid implant, EntityUid? implanted) + public ImplantImplantedEvent(EntityUid implant, EntityUid implanted) + { + Implant = implant; + Implanted = implanted; + } +} + +/// +/// Event that is raised whenever an implant is removed from someone. +/// Raised on the the implant entity. +/// + +[ByRefEvent] +public readonly record struct ImplantRemovedEvent +{ + public readonly EntityUid Implant; + public readonly 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 index 0000000000..748f9f3ad2 --- /dev/null +++ b/Content.Shared/Implants/StorageImplantSystem.cs @@ -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(OnImplantRemoved); + } + + private void OnImplantRemoved(Entity 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); + } + } +} diff --git a/Content.Shared/Mindshield/FakeMindShield/SharedFakeMindshieldSystem.cs b/Content.Shared/Mindshield/FakeMindShield/FakeMindshieldSystem.cs similarity index 94% rename from Content.Shared/Mindshield/FakeMindShield/SharedFakeMindshieldSystem.cs rename to Content.Shared/Mindshield/FakeMindShield/FakeMindshieldSystem.cs index c82f2b2863..4d7d457321 100644 --- a/Content.Shared/Mindshield/FakeMindShield/SharedFakeMindshieldSystem.cs +++ b/Content.Shared/Mindshield/FakeMindShield/FakeMindshieldSystem.cs @@ -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(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 index a597e03406..0000000000 --- a/Content.Shared/Mindshield/FakeMindShield/SharedFakeMindShieldImplantSystem.cs +++ /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(OnFakeMindShieldToggle); - SubscribeLocalEvent(ImplantCheck); - SubscribeLocalEvent(ImplantDraw); - } - - /// - /// Raise the Action of a Implanted user toggling their implant to the FakeMindshieldComponent on their entity - /// - private void OnFakeMindShieldToggle(Entity entity, ref FakeMindShieldToggleEvent ev) - { - ev.Handled = true; - if (entity.Comp.ImplantedEntity is not { } ent) - return; - - if (!TryComp(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(ev.Implanted.Value); - } - - private void ImplantDraw(Entity ent, ref EntGotRemovedFromContainerMessage ev) - { - RemComp(ev.Container.Owner); - } -} diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml index 97435c229d..c8ea03502a 100644 --- a/Resources/Prototypes/Actions/types.yml +++ b/Resources/Prototypes/Actions/types.yml @@ -376,6 +376,7 @@ iconOn: { sprite: Interface/Actions/actions_fakemindshield.rsi, state: icon-on } itemIconStyle: NoItem useDelay: 1 + raiseOnUser: true - type: InstantAction event: !type:FakeMindShieldToggleEvent - type: Tag diff --git a/Resources/Prototypes/Entities/Objects/Misc/implanters.yml b/Resources/Prototypes/Entities/Objects/Misc/implanters.yml index e1918ef5e6..316acba6fc 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/implanters.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/implanters.yml @@ -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 @@ -108,18 +108,18 @@ 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 @@ -128,30 +128,30 @@ 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 @@ -160,24 +160,24 @@ 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 @@ -186,8 +186,8 @@ name: tracking implanter parent: BaseImplantOnlyImplanter components: - - type: Implanter - implant: TrackingImplant + - type: Implanter + implant: TrackingImplant #Traitor implanters @@ -196,16 +196,16 @@ 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 @@ -228,24 +228,24 @@ 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 @@ -262,24 +262,24 @@ 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 @@ -304,8 +304,8 @@ name: mindshield implanter parent: BaseImplantOnlyImplanter components: - - type: Implanter - implant: MindShieldImplant + - type: Implanter + implant: MindShieldImplant # Centcomm implanters diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index 6a4ad24664..5ff8c79fb4 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -19,18 +19,18 @@ 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 @@ -39,20 +39,20 @@ 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 @@ -108,6 +108,7 @@ description: This implant grants hidden storage within a person's body using bluespace technology. categories: [ HideSpawnMenu ] components: + - type: StorageImplant - type: SubdermalImplant implantAction: ActionOpenStorageImplant whitelist: @@ -280,6 +281,10 @@ components: - type: SubdermalImplant permanent: true + - type: ReplacementImplant + whitelist: + tags: + - MicroBomb # replace microbomb implant with macrobomb - type: TriggerOnMobstateChange #activates the timer mobState: - Dead @@ -306,9 +311,9 @@ canCreateVacuum: true - type: Tag tags: - - SubdermalImplant - - HideContextMenu - - MacroBomb + - SubdermalImplant + - HideContextMenu + - MacroBomb - type: entity parent: BaseSubdermalImplant @@ -362,9 +367,11 @@ 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