diff --git a/Content.Server/Implants/RadioImplantSystem.cs b/Content.Server/Implants/RadioImplantSystem.cs new file mode 100644 index 0000000000..d9ba229057 --- /dev/null +++ b/Content.Server/Implants/RadioImplantSystem.cs @@ -0,0 +1,76 @@ +using Content.Server.Radio.Components; +using Content.Shared.Implants; +using Content.Shared.Implants.Components; +using Robust.Shared.Containers; + +namespace Content.Server.Implants; + +public sealed class RadioImplantSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnImplantImplanted); + SubscribeLocalEvent(OnRemove); + } + + /// + /// If implanted with a radio implant, installs the necessary intrinsic radio components + /// + private void OnImplantImplanted(Entity ent, ref ImplantImplantedEvent args) + { + if (args.Implanted == null) + return; + + var activeRadio = EnsureComp(args.Implanted.Value); + foreach (var channel in ent.Comp.RadioChannels) + { + if (activeRadio.Channels.Add(channel)) + ent.Comp.ActiveAddedChannels.Add(channel); + } + + EnsureComp(args.Implanted.Value); + + var intrinsicRadioTransmitter = EnsureComp(args.Implanted.Value); + foreach (var channel in ent.Comp.RadioChannels) + { + if (intrinsicRadioTransmitter.Channels.Add(channel)) + ent.Comp.TransmitterAddedChannels.Add(channel); + } + } + + /// + /// Removes intrinsic radio components once the Radio Implant is removed + /// + private void OnRemove(Entity ent, ref EntGotRemovedFromContainerMessage args) + { + if (TryComp(args.Container.Owner, out var activeRadioComponent)) + { + foreach (var channel in ent.Comp.ActiveAddedChannels) + { + activeRadioComponent.Channels.Remove(channel); + } + ent.Comp.ActiveAddedChannels.Clear(); + + if (activeRadioComponent.Channels.Count == 0) + { + RemCompDeferred(args.Container.Owner); + } + } + + if (!TryComp(args.Container.Owner, out var radioTransmitterComponent)) + return; + + foreach (var channel in ent.Comp.TransmitterAddedChannels) + { + radioTransmitterComponent.Channels.Remove(channel); + } + ent.Comp.TransmitterAddedChannels.Clear(); + + if (radioTransmitterComponent.Channels.Count == 0 || activeRadioComponent?.Channels.Count == 0) + { + RemCompDeferred(args.Container.Owner); + } + } +} diff --git a/Content.Shared/Implants/Components/RadioImplantComponent.cs b/Content.Shared/Implants/Components/RadioImplantComponent.cs new file mode 100644 index 0000000000..e0e7f55646 --- /dev/null +++ b/Content.Shared/Implants/Components/RadioImplantComponent.cs @@ -0,0 +1,37 @@ +using Content.Shared.Radio; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Implants.Components; + +/// +/// Gives the user access to a given channel without the need for a headset. +/// +[RegisterComponent] +public sealed partial class RadioImplantComponent : Component +{ + /// + /// The radio channel(s) to grant access to. + /// + [DataField(required: true)] + public HashSet> RadioChannels = new(); + + /// + /// The radio channels that have been added by the implant to a user's ActiveRadioComponent. + /// Used to track which channels were successfully added (not already in user) + /// + /// + /// Should not be modified outside RadioImplantSystem.cs + /// + [DataField] + public HashSet> ActiveAddedChannels = new(); + + /// + /// The radio channels that have been added by the implant to a user's IntrinsicRadioTransmitterComponent. + /// Used to track which channels were successfully added (not already in user) + /// + /// + /// Should not be modified outside RadioImplantSystem.cs + /// + [DataField] + public HashSet> TransmitterAddedChannels = new(); +} diff --git a/Resources/Locale/en-US/store/uplink-catalog.ftl b/Resources/Locale/en-US/store/uplink-catalog.ftl index 655c620e30..29b778b9e9 100644 --- a/Resources/Locale/en-US/store/uplink-catalog.ftl +++ b/Resources/Locale/en-US/store/uplink-catalog.ftl @@ -200,6 +200,9 @@ uplink-death-acidifier-implant-desc = Completely melts the user and their equipm uplink-micro-bomb-implanter-name = Micro Bomb Implanter uplink-micro-bomb-implanter-desc = Explode on death or manual activation with this implant. Destroys the body with all equipment. +uplink-radio-implanter-name = Radio Implanter +uplink-radio-implanter-desc = Implants a Syndicate radio, allowing covert communication without a headset. + # Bundles uplink-observation-kit-name = Observation Kit uplink-observation-kit-desc = Includes surveillance camera monitor board and security hud disguised as sunglasses. diff --git a/Resources/Prototypes/Catalog/uplink_catalog.yml b/Resources/Prototypes/Catalog/uplink_catalog.yml index f3ace5b0bb..6eafed2bc8 100644 --- a/Resources/Prototypes/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/Catalog/uplink_catalog.yml @@ -1328,6 +1328,20 @@ categories: - UplinkImplants +- type: listing + id: UplinkRadioImplanter + name: uplink-radio-implanter-name + description: uplink-radio-implanter-desc + icon: { sprite: /Textures/Objects/Devices/encryption_keys.rsi, state: synd_label } + productEntity: RadioImplanter + discountCategory: usualDiscounts + discountDownTo: + Telecrystal: 1 + cost: + Telecrystal: 2 + categories: + - UplinkImplants + - type: listing id: UplinkMicroBombImplanter name: uplink-micro-bomb-implanter-name diff --git a/Resources/Prototypes/Entities/Objects/Misc/implanters.yml b/Resources/Prototypes/Entities/Objects/Misc/implanters.yml index b8ab92e5e9..d40f48dc7d 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/implanters.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/implanters.yml @@ -179,6 +179,14 @@ - type: Implanter implant: FreedomImplant +- type: entity + id: RadioImplanter + suffix: radio Syndicate + parent: BaseImplantOnlyImplanterSyndi + components: + - type: Implanter + implant: RadioImplant + - type: entity id: UplinkImplanter suffix: uplink @@ -262,3 +270,13 @@ components: - type: Implanter implant: MindShieldImplant + +# Centcomm implanters + +- type: entity + id: RadioImplanterCentcomm + suffix: radio Centcomm + parent: BaseImplantOnlyImplanter + components: + - type: Implanter + implant: RadioImplantCentcomm diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index 5d93cefd5d..c4e2701769 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -142,6 +142,18 @@ components: - Cuffable # useless if you cant be cuffed +- type: entity + parent: BaseSubdermalImplant + id: RadioImplant + name: radio implant + description: This implant grants access to the Syndicate channel without a headset. + categories: [ HideSpawnMenu ] + components: + - type: SubdermalImplant + - type: RadioImplant + radioChannels: + - Syndicate + - type: entity parent: [ BaseSubdermalImplant, StorePresetUplink ] id: UplinkImplant @@ -336,3 +348,17 @@ - type: Tag tags: - MindShield + +# Centcomm implants + +- type: entity + parent: BaseSubdermalImplant + id: RadioImplantCentcomm + name: radio implant + description: This implant grants access to the Centcomm channel without a headset. Only authorized for Centcomm employees. + categories: [ HideSpawnMenu ] + components: + - type: SubdermalImplant + - type: RadioImplant + radioChannels: + - CentCom