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))
{
// TODO: Don't use protoId for deimplanting
// and especially not raw strings!
Dictionary<string, string> implants = new();
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();
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>

View 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)

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

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

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

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

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

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.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 (!_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);
}
/// <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,60 +137,25 @@ 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)
public void WipeImplants(Entity<ImplantedComponent?> target)
{
if (!TryComp<ImplantedComponent>(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<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;
_container.CleanContainer(target.Comp.ImplantContainer);
}
}
@@ -212,12 +167,30 @@ public sealed class ImplantRelayEvent<T> where T : notnull
/// implant implant implant implant
/// </remarks>
[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;
}
}
/// <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;
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;
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);
}

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 }
itemIconStyle: NoItem
useDelay: 1
raiseOnUser: true
- type: InstantAction
event: !type:FakeMindShieldToggleEvent
- type: Tag

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

View File

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