diff --git a/Content.Server/Implants/ImplanterSystem.cs b/Content.Server/Implants/ImplanterSystem.cs index f3072769e4..0d46241f41 100644 --- a/Content.Server/Implants/ImplanterSystem.cs +++ b/Content.Server/Implants/ImplanterSystem.cs @@ -1,11 +1,9 @@ -using Content.Server.Guardian; using Content.Server.Popups; using Content.Shared.DoAfter; using Content.Shared.IdentityManagement; using Content.Shared.Implants; using Content.Shared.Implants.Components; using Content.Shared.Interaction; -using Content.Shared.Mobs.Components; using Content.Shared.Popups; using Robust.Shared.Containers; @@ -33,28 +31,39 @@ public sealed partial class ImplanterSystem : SharedImplanterSystem if (args.Target == null || !args.CanReach || args.Handled) return; - //Simplemobs and regular mobs should be injectable, but only regular mobs have mind. - //So just don't implant/draw anything that isn't living or is a guardian - //TODO: Rework a bit when surgery is in to work with implant cases - if (!HasComp(args.Target.Value) || HasComp(args.Target.Value)) + var target = args.Target.Value; + if (!CheckTarget(target, component.Whitelist, component.Blacklist)) return; //TODO: Rework when surgery is in for implant cases if (component.CurrentMode == ImplanterToggleMode.Draw && !component.ImplantOnly) { - TryDraw(component, args.User, args.Target.Value, uid); + TryDraw(component, args.User, target, uid); } else { - if (!CanImplant(args.User, args.Target.Value, uid, component, out _, out _)) + if (!CanImplant(args.User, target, uid, component, out var implant, out _)) + { + // no popup if implant doesn't exist + if (implant == null) + return; + + // show popup to the user saying implant failed + var name = Identity.Name(target, EntityManager, args.User); + var msg = Loc.GetString("implanter-component-implant-failed", ("implant", implant), ("target", name)); + _popup.PopupEntity(msg, target, args.User); + // prevent further interaction since popup was shown + args.Handled = true; return; + } //Implant self instantly, otherwise try to inject the target. - if (args.User == args.Target) - Implant(args.User, args.Target.Value, uid, component); + if (args.User == target) + Implant(target, target, uid, component); else - TryImplant(component, args.User, args.Target.Value, uid); + TryImplant(component, args.User, target, uid); } + args.Handled = true; } diff --git a/Content.Shared/Implants/Components/ImplanterComponent.cs b/Content.Shared/Implants/Components/ImplanterComponent.cs index 85891826f9..32a3636163 100644 --- a/Content.Shared/Implants/Components/ImplanterComponent.cs +++ b/Content.Shared/Implants/Components/ImplanterComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Containers.ItemSlots; +using Content.Shared.Whitelist; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -15,6 +16,19 @@ public sealed partial class ImplanterComponent : Component public const string ImplanterSlotId = "implanter_slot"; public const string ImplantSlotId = "implant"; + /// + /// Whitelist to check entities against before implanting. + /// Implants get their own whitelist which is checked afterwards. + /// + [DataField, AutoNetworkedField] + public EntityWhitelist? Whitelist; + + /// + /// Blacklist to check entities against before implanting. + /// + [DataField, AutoNetworkedField] + public EntityWhitelist? Blacklist; + /// /// Used for implanters that start with specific implants /// diff --git a/Content.Shared/Implants/Components/SubdermalImplantComponent.cs b/Content.Shared/Implants/Components/SubdermalImplantComponent.cs index b2fdb14e4c..5edc26ead3 100644 --- a/Content.Shared/Implants/Components/SubdermalImplantComponent.cs +++ b/Content.Shared/Implants/Components/SubdermalImplantComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Actions; +using Content.Shared.Whitelist; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -34,6 +35,20 @@ public sealed partial class SubdermalImplantComponent : Component [ViewVariables(VVAccess.ReadWrite)] [DataField("permanent"), AutoNetworkedField] public bool Permanent = false; + + /// + /// Target whitelist for this implant specifically. + /// Only checked if the implanter allows implanting on the target to begin with. + /// + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// Target blacklist for this implant specifically. + /// Only checked if the implanter allows implanting on the target to begin with. + /// + [DataField] + public EntityWhitelist? Blacklist; } /// diff --git a/Content.Shared/Implants/SharedImplanterSystem.cs b/Content.Shared/Implants/SharedImplanterSystem.cs index 1cf9f44663..404e6da508 100644 --- a/Content.Shared/Implants/SharedImplanterSystem.cs +++ b/Content.Shared/Implants/SharedImplanterSystem.cs @@ -6,6 +6,7 @@ using Content.Shared.Examine; using Content.Shared.IdentityManagement; using Content.Shared.Implants.Components; using Content.Shared.Popups; +using Content.Shared.Whitelist; using Robust.Shared.Containers; using Robust.Shared.Serialization; @@ -85,11 +86,23 @@ public abstract class SharedImplanterSystem : EntitySystem if (!TryComp(implant, out implantComp)) return false; + if (!CheckTarget(target, component.Whitelist, component.Blacklist) || + !CheckTarget(target, implantComp.Whitelist, implantComp.Blacklist)) + { + return false; + } + var ev = new AddImplantAttemptEvent(user, target, implant.Value, implanter); RaiseLocalEvent(target, ev); return !ev.Cancelled; } + protected bool CheckTarget(EntityUid target, EntityWhitelist? whitelist, EntityWhitelist? blacklist) + { + return whitelist?.IsValid(target, EntityManager) != false && + blacklist?.IsValid(target, EntityManager) != true; + } + //Draw the implant out of the target //TODO: Rework when surgery is in so implant cases can be a thing public void Draw(EntityUid implanter, EntityUid user, EntityUid target, ImplanterComponent component) diff --git a/Resources/Locale/en-US/implant/implant.ftl b/Resources/Locale/en-US/implant/implant.ftl index bdc82c291d..22db4460af 100644 --- a/Resources/Locale/en-US/implant/implant.ftl +++ b/Resources/Locale/en-US/implant/implant.ftl @@ -1,6 +1,7 @@ ## Implanter Attempt Messages implanter-component-implanting-target = {$user} is trying to implant you with something! +implanter-component-implant-failed = The {$implant} cannot be given to {$target}! implanter-draw-failed-permanent = The {$implant} in {$target} is fused with them and cannot be removed! implanter-draw-failed = You tried to remove an implant but found nothing. diff --git a/Resources/Prototypes/Entities/Objects/Misc/implanters.yml b/Resources/Prototypes/Entities/Objects/Misc/implanters.yml index 41966ab93f..0ca029a120 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/implanters.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/implanters.yml @@ -12,6 +12,12 @@ containers: implanter_slot: !type:ContainerSlot { } - type: Implanter + whitelist: + components: + - Body # no chair microbomb + blacklist: + components: + - Guardian # no holoparasite macrobomb wombo combo currentMode: Draw implanterSlot: name: Implant @@ -54,6 +60,16 @@ tags: - Trash +- type: entity + parent: Implanter + id: ImplanterAdmeme + suffix: Admeme + components: + - type: Implanter + # go wild with sentient chairs with macrobombs + whitelist: null + blacklist: null + - type: entity id: BaseImplantOnlyImplanter parent: Implanter diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index 6632010a79..bc7a3a38ad 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -21,6 +21,9 @@ noSpawn: true 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 @@ -81,6 +84,9 @@ noSpawn: true components: - type: SubdermalImplant + whitelist: + components: + - MobState # admeme implanting a chair with tracking implant needs to give the chair mobstate so it can die first - type: SuitSensor randomMode: false controlsLocked: true @@ -109,6 +115,9 @@ components: - type: SubdermalImplant implantAction: ActionOpenStorageImplant + whitelist: + components: + - Hands # no use giving a mouse a storage implant, but a monkey is another story... - type: Item size: 9999 - type: Storage @@ -131,6 +140,9 @@ components: - type: SubdermalImplant implantAction: ActionActivateFreedomImplant + whitelist: + components: + - Cuffable # useless if you cant be cuffed - type: entity parent: BaseSubdermalImplant @@ -141,6 +153,9 @@ components: - type: SubdermalImplant implantAction: ActionOpenUplinkImplant + whitelist: + components: + - Hands # prevent mouse buying grenade penguin since its not telepathic - type: Store preset: StorePresetUplink balance: @@ -174,6 +189,9 @@ components: - type: SubdermalImplant implantAction: ActionActivateDnaScramblerImplant + whitelist: + components: + - HumanoidAppearance # syndies cant turn hamlet into a human #Nuclear Operative/Special Exclusive implants @@ -250,6 +268,9 @@ components: - type: SubdermalImplant permanent: true + whitelist: + components: + - MobState # admeme implanting a chair with rattle implant needs to give the chair mobstate so it can die first - type: TriggerOnMobstateChange mobState: - Critical