Cleanup subdermal implant code (#39755)

This commit is contained in:
slarticodefast
2025-08-20 13:52:03 +02:00
committed by GitHub
parent db84d766e9
commit 2bbff7f8c0
20 changed files with 462 additions and 368 deletions

View File

@@ -23,6 +23,8 @@ public sealed class ImplanterSystem : SharedImplanterSystem
{ {
if (_uiSystem.TryGetOpenUi<DeimplantBoundUserInterface>(uid, DeimplantUiKey.Key, out var bui)) 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(); Dictionary<string, string> implants = new();
foreach (var implant in component.DeimplantWhitelist) foreach (var implant in component.DeimplantWhitelist)
{ {

View File

@@ -0,0 +1,5 @@
using Content.Shared.Implants;
namespace Content.Client.Implants;
public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem;

View File

@@ -29,17 +29,17 @@ public sealed class ChameleonControllerSystem : SharedChameleonControllerSystem
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<SubdermalImplantComponent, ChameleonControllerSelectedOutfitMessage>(OnSelected); SubscribeLocalEvent<ChameleonControllerImplantComponent, ChameleonControllerSelectedOutfitMessage>(OnSelected);
SubscribeLocalEvent<ChameleonClothingComponent, InventoryRelayedEvent<ChameleonControllerOutfitSelectedEvent>>(ChameleonControllerOutfitItemSelected); 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; return;
ChangeChameleonClothingToOutfit(ent.Comp.ImplantedEntity.Value, args.SelectedChameleonOutfit); ChangeChameleonClothingToOutfit(implantComp.ImplantedEntity.Value, args.SelectedChameleonOutfit);
} }
/// <summary> /// <summary>

View File

@@ -27,6 +27,7 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem
SubscribeLocalEvent<ImplanterComponent, DrawEvent>(OnDraw); SubscribeLocalEvent<ImplanterComponent, DrawEvent>(OnDraw);
} }
// TODO: This all needs to be moved to shared and predicted.
private void OnImplanterAfterInteract(EntityUid uid, ImplanterComponent component, AfterInteractEvent args) private void OnImplanterAfterInteract(EntityUid uid, ImplanterComponent component, AfterInteractEvent args)
{ {
if (args.Target == null || !args.CanReach || args.Handled) if (args.Target == null || !args.CanReach || args.Handled)

View File

@@ -1,7 +1,6 @@
using Content.Server.Radio.Components; using Content.Server.Radio.Components;
using Content.Shared.Implants; using Content.Shared.Implants;
using Content.Shared.Implants.Components; using Content.Shared.Implants.Components;
using Robust.Shared.Containers;
namespace Content.Server.Implants; namespace Content.Server.Implants;
@@ -12,7 +11,7 @@ public sealed class RadioImplantSystem : EntitySystem
base.Initialize(); base.Initialize();
SubscribeLocalEvent<RadioImplantComponent, ImplantImplantedEvent>(OnImplantImplanted); SubscribeLocalEvent<RadioImplantComponent, ImplantImplantedEvent>(OnImplantImplanted);
SubscribeLocalEvent<RadioImplantComponent, EntGotRemovedFromContainerMessage>(OnRemove); SubscribeLocalEvent<RadioImplantComponent, ImplantRemovedEvent>(OnImplantRemoved);
} }
/// <summary> /// <summary>
@@ -20,19 +19,16 @@ public sealed class RadioImplantSystem : EntitySystem
/// </summary> /// </summary>
private void OnImplantImplanted(Entity<RadioImplantComponent> ent, ref ImplantImplantedEvent args) private void OnImplantImplanted(Entity<RadioImplantComponent> ent, ref ImplantImplantedEvent args)
{ {
if (args.Implanted == null) var activeRadio = EnsureComp<ActiveRadioComponent>(args.Implanted);
return;
var activeRadio = EnsureComp<ActiveRadioComponent>(args.Implanted.Value);
foreach (var channel in ent.Comp.RadioChannels) foreach (var channel in ent.Comp.RadioChannels)
{ {
if (activeRadio.Channels.Add(channel)) if (activeRadio.Channels.Add(channel))
ent.Comp.ActiveAddedChannels.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) foreach (var channel in ent.Comp.RadioChannels)
{ {
if (intrinsicRadioTransmitter.Channels.Add(channel)) if (intrinsicRadioTransmitter.Channels.Add(channel))
@@ -43,9 +39,9 @@ public sealed class RadioImplantSystem : EntitySystem
/// <summary> /// <summary>
/// Removes intrinsic radio components once the Radio Implant is removed /// Removes intrinsic radio components once the Radio Implant is removed
/// </summary> /// </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) foreach (var channel in ent.Comp.ActiveAddedChannels)
{ {
@@ -55,11 +51,11 @@ public sealed class RadioImplantSystem : EntitySystem
if (activeRadioComponent.Channels.Count == 0) 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; return;
foreach (var channel in ent.Comp.TransmitterAddedChannels) 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) if (radioTransmitterComponent.Channels.Count == 0 || activeRadioComponent?.Channels.Count == 0)
{ {
RemCompDeferred<IntrinsicRadioTransmitterComponent>(args.Container.Owner); RemCompDeferred<IntrinsicRadioTransmitterComponent>(args.Implanted);
} }
} }
} }

View File

@@ -18,6 +18,7 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem
SubscribeLocalEvent<StoreComponent, ImplantRelayEvent<AfterInteractUsingEvent>>(OnStoreRelay); SubscribeLocalEvent<StoreComponent, ImplantRelayEvent<AfterInteractUsingEvent>>(OnStoreRelay);
} }
// TODO: This shouldn't be in the SubdermalImplantSystem
private void OnStoreRelay(EntityUid uid, StoreComponent store, ImplantRelayEvent<AfterInteractUsingEvent> implantRelay) private void OnStoreRelay(EntityUid uid, StoreComponent store, ImplantRelayEvent<AfterInteractUsingEvent> implantRelay)
{ {
var args = implantRelay.Event; var args = implantRelay.Event;

View File

@@ -27,7 +27,7 @@ public sealed class MindShieldSystem : EntitySystem
base.Initialize(); base.Initialize();
SubscribeLocalEvent<MindShieldImplantComponent, ImplantImplantedEvent>(OnImplantImplanted); SubscribeLocalEvent<MindShieldImplantComponent, ImplantImplantedEvent>(OnImplantImplanted);
SubscribeLocalEvent<MindShieldImplantComponent, EntGotRemovedFromContainerMessage>(OnImplantDraw); SubscribeLocalEvent<MindShieldImplantComponent, ImplantRemovedEvent>(OnImplantRemoved);
} }
private void OnImplantImplanted(Entity<MindShieldImplantComponent> ent, ref ImplantImplantedEvent ev) private void OnImplantImplanted(Entity<MindShieldImplantComponent> ent, ref ImplantImplantedEvent ev)
@@ -35,8 +35,8 @@ public sealed class MindShieldSystem : EntitySystem
if (ev.Implanted == null) if (ev.Implanted == null)
return; return;
EnsureComp<MindShieldComponent>(ev.Implanted.Value); EnsureComp<MindShieldComponent>(ev.Implanted);
MindShieldRemovalCheck(ev.Implanted.Value, ev.Implant); MindShieldRemovalCheck(ev.Implanted, ev.Implant);
} }
/// <summary> /// <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);
} }
} }

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -16,13 +16,21 @@ public sealed partial class SubdermalImplantComponent : Component
/// <summary> /// <summary>
/// Used where you want the implant to grant the owner an instant action. /// Used where you want the implant to grant the owner an instant action.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField]
[DataField("implantAction")]
public EntProtoId? ImplantAction; public EntProtoId? ImplantAction;
/// <summary>
/// The provided action entity.
/// </summary>
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public EntityUid? Action; public EntityUid? Action;
/// <summary>
/// Components to add/remove to the implantee when the implant is injected/extracted.
/// </summary>
[DataField]
public ComponentRegistry ImplantComponents = new();
/// <summary> /// <summary>
/// The entity this implant is inside /// The entity this implant is inside
/// </summary> /// </summary>
@@ -32,8 +40,7 @@ public sealed partial class SubdermalImplantComponent : Component
/// <summary> /// <summary>
/// Should this implant be removeable? /// Should this implant be removeable?
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField, AutoNetworkedField]
[DataField("permanent"), AutoNetworkedField]
public bool Permanent = false; public bool Permanent = false;
/// <summary> /// <summary>
@@ -61,23 +68,20 @@ public sealed partial class SubdermalImplantComponent : Component
/// <summary> /// <summary>
/// Used for opening the storage implant via action. /// Used for opening the storage implant via action.
/// </summary> /// </summary>
public sealed partial class OpenStorageImplantEvent : InstantActionEvent /// <remarks>
{ /// TODO: Delete this and just add a ToggleUIOnTriggerComponent
/// </remarks>
} public sealed partial class OpenStorageImplantEvent : InstantActionEvent;
/// <summary> /// <summary>
/// Used for triggering trigger events on the implant via action /// Used for triggering trigger events on the implant via action
/// </summary> /// </summary>
public sealed partial class ActivateImplantEvent : InstantActionEvent public sealed partial class ActivateImplantEvent : InstantActionEvent;
{
}
/// <summary> /// <summary>
/// Used for opening the uplink implant via action. /// Used for opening the uplink implant via action.
/// </summary> /// </summary>
public sealed partial class OpenUplinkImplantEvent : InstantActionEvent /// <remarks>
{ /// TODO: Delete this and just add a ToggleUIOnTriggerComponent
/// </remarks>
} public sealed partial class OpenUplinkImplantEvent : InstantActionEvent;

View File

@@ -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);
}
}
}

View File

@@ -118,7 +118,7 @@ public abstract class SharedImplanterSystem : EntitySystem
//Set to draw mode if not implant only //Set to draw mode if not implant only
public void Implant(EntityUid user, EntityUid target, EntityUid implanter, ImplanterComponent component) 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; return;
// Check if we are trying to implant a implant which is already implanted // 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) if (component.ImplanterSlot.ContainerSlot != null)
_container.Remove(implant.Value, component.ImplanterSlot.ContainerSlot); _container.Remove(implant.Value, component.ImplanterSlot.ContainerSlot);
implantComp.ImplantedEntity = target;
implantContainer.OccludesLight = false; implantContainer.OccludesLight = false;
_container.Insert(implant.Value, implantContainer); _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) private void DrawImplantIntoImplanter(EntityUid implanter, EntityUid target, EntityUid implant, BaseContainer implantContainer, ContainerSlot implanterContainer, SubdermalImplantComponent implantComp)
{ {
_container.Remove(implant, implantContainer); _container.Remove(implant, implantContainer);
implantComp.ImplantedEntity = null;
_container.Insert(implant, implanterContainer); _container.Insert(implant, implanterContainer);
var ev = new TransferDnaEvent { Donor = target, Recipient = implanter }; var ev = new TransferDnaEvent { Donor = target, Recipient = implanter };

View File

@@ -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;
}
}

View File

@@ -1,90 +1,76 @@
using System.Linq;
using Content.Shared.Actions; using Content.Shared.Actions;
using Content.Shared.Implants.Components; 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.Containers;
using Robust.Shared.Network;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Shared.Implants; 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 SharedContainerSystem _container = default!;
[Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly INetManager _net = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly IGameTiming _timing = default!;
public const string BaseStorageId = "storagebase";
private static readonly ProtoId<TagPrototype> MicroBombTag = "MicroBomb";
private static readonly ProtoId<TagPrototype> MacroBombTag = "MacroBomb";
public override void Initialize() public override void Initialize()
{ {
base.Initialize();
InitializeRelay();
SubscribeLocalEvent<SubdermalImplantComponent, EntGotInsertedIntoContainerMessage>(OnInsert); SubscribeLocalEvent<SubdermalImplantComponent, EntGotInsertedIntoContainerMessage>(OnInsert);
SubscribeLocalEvent<SubdermalImplantComponent, ContainerGettingRemovedAttemptEvent>(OnRemoveAttempt); SubscribeLocalEvent<SubdermalImplantComponent, ContainerGettingRemovedAttemptEvent>(OnRemoveAttempt);
SubscribeLocalEvent<SubdermalImplantComponent, EntGotRemovedFromContainerMessage>(OnRemove); 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; return;
if (!string.IsNullOrWhiteSpace(component.ImplantAction)) if (args.Container.ID != ImplanterComponent.ImplantSlotId)
{ return;
_actionsSystem.AddAction(component.ImplantedEntity.Value, ref component.Action, component.ImplantAction, uid);
ent.Comp.ImplantedEntity = args.Container.Owner;
Dirty(ent);
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);
} }
// replace micro bomb with macro bomb private void OnRemoveAttempt(Entity<SubdermalImplantComponent> ent, ref ContainerGettingRemovedAttemptEvent args)
// 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 (ent.Comp.Permanent && ent.Comp.ImplantedEntity != null)
{
if (_tag.HasTag(implant, MicroBombTag))
{
_container.Remove(implant, implantContainer);
PredictedQueueDel(implant);
}
}
}
var ev = new ImplantImplantedEvent(uid, component.ImplantedEntity.Value);
RaiseLocalEvent(uid, ref ev);
}
private void OnRemoveAttempt(EntityUid uid, SubdermalImplantComponent component, ContainerGettingRemovedAttemptEvent args)
{
if (component.Permanent && component.ImplantedEntity != null)
args.Cancel(); 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; return;
if (component.ImplantAction != null) if (args.Container.ID != ImplanterComponent.ImplantSlotId)
_actionsSystem.RemoveProvidedActions(component.ImplantedEntity.Value, uid);
if (!_container.TryGetContainer(uid, BaseStorageId, out var storageImplant))
return; return;
var containedEntites = storageImplant.ContainedEntities.ToArray(); if (ent.Comp.ImplantedEntity == null || Terminating(ent.Comp.ImplantedEntity.Value))
return;
foreach (var entity in containedEntites) EntityManager.RemoveComponents(ent.Comp.ImplantedEntity.Value, ent.Comp.ImplantComponents);
{ _actions.RemoveAction(ent.Comp.ImplantedEntity.Value, ent.Comp.Action);
_transformSystem.DropNextTo(entity, uid); 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);
} }
/// <summary> /// <summary>
@@ -106,23 +92,26 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
/// <returns> /// <returns>
/// The implant, if it was successfully created. Otherwise, null. /// The implant, if it was successfully created. Otherwise, null.
/// </returns>> /// </returns>>
public EntityUid? AddImplant(EntityUid uid, String implantId) public EntityUid? AddImplant(EntityUid target, EntProtoId implantId)
{ {
var coords = Transform(uid).Coordinates; if (_net.IsClient)
var ent = Spawn(implantId, coords); 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 else
{ {
Log.Warning($"Found invalid starting implant '{implantId}' on {uid} {ToPrettyString(uid):implanted}"); Log.Warning($"Tried to inject implant '{implantId}' without SubdermalImplantComponent into {ToPrettyString(target):implanted}");
Del(ent); Del(implant);
return null; return null;
} }
return ent; return implant;
} }
/// <summary> /// <summary>
@@ -131,15 +120,16 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
/// </summary> /// </summary>
/// <param name="target">The entity to be implanted</param> /// <param name="target">The entity to be implanted</param>
/// <param name="implant"> The implant</param> /// <param name="implant"> The implant</param>
/// <param name="component">The implant component</param> public void ForceImplant(EntityUid target, Entity<SubdermalImplantComponent?> implant)
public void ForceImplant(EntityUid target, EntityUid implant, SubdermalImplantComponent component)
{ {
if (!Resolve(implant, ref implant.Comp))
return;
//If the target doesn't have the implanted component, add it. //If the target doesn't have the implanted component, add it.
var implantedComp = EnsureComp<ImplantedComponent>(target); var implantedComp = EnsureComp<ImplantedComponent>(target);
var implantContainer = implantedComp.ImplantContainer;
component.ImplantedEntity = target; implant.Comp.ImplantedEntity = target;
_container.Insert(implant, implantContainer); _container.Insert(implant.Owner, implantedComp.ImplantContainer);
} }
/// <summary> /// <summary>
@@ -147,60 +137,25 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem
/// </summary> /// </summary>
/// <param name="target">the implanted entity</param> /// <param name="target">the implanted entity</param>
/// <param name="implant">the implant</param> /// <param name="implant">the implant</param>
[PublicAPI] public void ForceRemove(Entity<ImplantedComponent?> target, EntityUid implant)
public void ForceRemove(EntityUid target, EntityUid implant)
{ {
if (!TryComp<ImplantedComponent>(target, out var implanted)) if (!Resolve(target, ref target.Comp))
return; return;
var implantContainer = implanted.ImplantContainer; _container.Remove(implant, target.Comp.ImplantContainer);
PredictedQueueDel(implant);
_container.Remove(implant, implantContainer);
QueueDel(implant);
} }
/// <summary> /// <summary>
/// Removes and deletes implants by force /// Removes and deletes implants by force
/// </summary> /// </summary>
/// <param name="target">The entity to have implants removed</param> /// <param name="target">The entity to have implants removed</param>
[PublicAPI] public void WipeImplants(Entity<ImplantedComponent?> target)
public void WipeImplants(EntityUid target)
{ {
if (!TryComp<ImplantedComponent>(target, out var implanted)) if (!Resolve(target, ref target.Comp, false))
return; return;
var implantContainer = implanted.ImplantContainer; _container.CleanContainer(target.Comp.ImplantContainer);
_container.CleanContainer(implantContainer);
}
//Relays from the implanted to the implant
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);
}
}
}
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;
} }
} }
@@ -212,12 +167,30 @@ public sealed class ImplantRelayEvent<T> where T : notnull
/// implant implant implant implant /// implant implant implant implant
/// </remarks> /// </remarks>
[ByRefEvent] [ByRefEvent]
public readonly struct ImplantImplantedEvent public readonly record struct ImplantImplantedEvent
{ {
public readonly EntityUid Implant; 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;
}
}
/// <summary>
/// Event that is raised whenever an implant is removed from someone.
/// Raised on the the implant entity.
/// </summary>
[ByRefEvent]
public readonly record struct ImplantRemovedEvent
{
public readonly EntityUid Implant;
public readonly EntityUid Implanted;
public ImplantRemovedEvent(EntityUid implant, EntityUid implanted)
{ {
Implant = implant; Implant = implant;
Implanted = implanted; Implanted = implanted;

View File

@@ -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);
}
}
}

View File

@@ -8,7 +8,7 @@ using Robust.Shared.Timing;
namespace Content.Shared.Mindshield.FakeMindShield; namespace Content.Shared.Mindshield.FakeMindShield;
public sealed class SharedFakeMindShieldSystem : EntitySystem public sealed class FakeMindShieldSystem : EntitySystem
{ {
[Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly TagSystem _tag = default!; [Dependency] private readonly TagSystem _tag = default!;
@@ -24,9 +24,11 @@ public sealed class SharedFakeMindShieldSystem : EntitySystem
SubscribeLocalEvent<FakeMindShieldComponent, ChameleonControllerOutfitSelectedEvent>(OnChameleonControllerOutfitSelected); 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; comp.IsEnabled = !comp.IsEnabled;
args.Toggle = true;
args.Handled = true;
Dirty(uid, comp); Dirty(uid, comp);
} }

View File

@@ -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);
}
}

View File

@@ -376,6 +376,7 @@
iconOn: { sprite: Interface/Actions/actions_fakemindshield.rsi, state: icon-on } iconOn: { sprite: Interface/Actions/actions_fakemindshield.rsi, state: icon-on }
itemIconStyle: NoItem itemIconStyle: NoItem
useDelay: 1 useDelay: 1
raiseOnUser: true
- type: InstantAction - type: InstantAction
event: !type:FakeMindShieldToggleEvent event: !type:FakeMindShieldToggleEvent
- type: Tag - type: Tag

View File

@@ -108,6 +108,7 @@
description: This implant grants hidden storage within a person's body using bluespace technology. description: This implant grants hidden storage within a person's body using bluespace technology.
categories: [ HideSpawnMenu ] categories: [ HideSpawnMenu ]
components: components:
- type: StorageImplant
- type: SubdermalImplant - type: SubdermalImplant
implantAction: ActionOpenStorageImplant implantAction: ActionOpenStorageImplant
whitelist: whitelist:
@@ -280,6 +281,10 @@
components: components:
- type: SubdermalImplant - type: SubdermalImplant
permanent: true permanent: true
- type: ReplacementImplant
whitelist:
tags:
- MicroBomb # replace microbomb implant with macrobomb
- type: TriggerOnMobstateChange #activates the timer - type: TriggerOnMobstateChange #activates the timer
mobState: mobState:
- Dead - Dead
@@ -364,6 +369,8 @@
components: components:
- type: SubdermalImplant - type: SubdermalImplant
implantAction: FakeMindShieldToggleAction implantAction: FakeMindShieldToggleAction
implantComponents:
- type: FakeMindShield # TODO: put the component on the implant and use implant relay events for the status icon
- type: FakeMindShieldImplant - type: FakeMindShieldImplant
# Sec and Command implants # Sec and Command implants