diff --git a/Content.Server/Radio/Components/IntrinsicRadioReceiverComponent.cs b/Content.Server/Radio/Components/IntrinsicRadioReceiverComponent.cs index 416dfe39cb86..d171c800f8b6 100644 --- a/Content.Server/Radio/Components/IntrinsicRadioReceiverComponent.cs +++ b/Content.Server/Radio/Components/IntrinsicRadioReceiverComponent.cs @@ -1,3 +1,6 @@ +using Content.Shared.Radio; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; + namespace Content.Server.Radio.Components; /// @@ -8,4 +11,11 @@ namespace Content.Server.Radio.Components; [RegisterComponent] public sealed partial class IntrinsicRadioReceiverComponent : Component { + //SS220 PAI with encryption keys begin + /// + /// Channels that will not be deleted from when extracting the encryption key from the entity + /// + [DataField("channels", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] + public HashSet Channels = new(); + //SS220 PAI with encryption keys end } diff --git a/Content.Server/Radio/Components/IntrinsicRadioTransmitterComponent.cs b/Content.Server/Radio/Components/IntrinsicRadioTransmitterComponent.cs index 13cc090663ef..e525a366da55 100644 --- a/Content.Server/Radio/Components/IntrinsicRadioTransmitterComponent.cs +++ b/Content.Server/Radio/Components/IntrinsicRadioTransmitterComponent.cs @@ -14,4 +14,12 @@ public sealed partial class IntrinsicRadioTransmitterComponent : Component { [DataField("channels", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] public HashSet Channels = new() { SharedChatSystem.CommonChannel }; + + //SS220 PAI with encryption keys begin + /// + /// Channels that an entity can use by encryption keys + /// + [ViewVariables] + public HashSet EncryptionKeyChannels = new(); + //SS220 PAI with encryption keys end } diff --git a/Content.Server/Radio/EntitySystems/RadioSystem.cs b/Content.Server/Radio/EntitySystems/RadioSystem.cs index 3ecdf982f50d..cbe319ce3cc6 100644 --- a/Content.Server/Radio/EntitySystems/RadioSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioSystem.cs @@ -51,12 +51,18 @@ public override void Initialize() SubscribeLocalEvent(OnIntrinsicReceive); SubscribeLocalEvent(OnIntrinsicSpeak); + //SS220 PAI with encryption keys begin + SubscribeLocalEvent(OnEncryptionChannelsChangeReceiver); + SubscribeLocalEvent(OnEncryptionChannelsChangeTransmitter); + //SS220 PAI with encryption keys end + _exemptQuery = GetEntityQuery(); } private void OnIntrinsicSpeak(EntityUid uid, IntrinsicRadioTransmitterComponent component, EntitySpokeEvent args) { - if (args.Channel != null && component.Channels.Contains(args.Channel.ID)) + if (args.Channel != null && (component.Channels.Contains(args.Channel.ID) || + component.EncryptionKeyChannels.Contains(args.Channel.ID))) //SS220 PAI with encryption keys { SendRadioMessage(uid, args.Message, args.Channel, uid); args.Channel = null; // prevent duplicate messages from other listeners. @@ -247,4 +253,25 @@ private bool HasActiveServer(MapId mapId, string channelId) } return false; } + + //SS220 PAI with encryption keys begin + private void OnEncryptionChannelsChangeTransmitter(Entity entity, ref EncryptionChannelsChangedEvent args) + { + if (args.Component.Channels.Count == 0) + entity.Comp.EncryptionKeyChannels.Clear(); + else + entity.Comp.EncryptionKeyChannels = new(args.Component.Channels); + } + + private void OnEncryptionChannelsChangeReceiver(Entity entity, ref EncryptionChannelsChangedEvent args) + { + if (!TryComp(entity.Owner, out var activeRadio)) + return; + + HashSet channels = entity.Comp.Channels; + channels.UnionWith(args.Component.Channels); + + activeRadio.Channels = new(channels); + } + //SS220 PAI with encryption keys end } diff --git a/Resources/Locale/ru-RU/ss220/prototypes/entities/objects/fun/pai.ftl b/Resources/Locale/ru-RU/ss220/prototypes/entities/objects/fun/pai.ftl new file mode 100644 index 000000000000..2501fc637ecd --- /dev/null +++ b/Resources/Locale/ru-RU/ss220/prototypes/entities/objects/fun/pai.ftl @@ -0,0 +1,2 @@ +ent-EncryptionKeysPersonalAI = { ent-PersonalAI } + .desc = { ent-PersonalAI.desc } diff --git a/Resources/Prototypes/Entities/Objects/Fun/pai.yml b/Resources/Prototypes/Entities/Objects/Fun/pai.yml index eccd1d53924a..f6b65807af52 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/pai.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/pai.yml @@ -3,6 +3,7 @@ - type: entity parent: BaseItem id: PersonalAI + noSpawn: true #SS220 PAI with encryption keys name: personal ai device description: Your electronic pal who's fun to be with! components: @@ -99,6 +100,16 @@ - type: ActiveRadio channels: - Syndicate + #SS220 PAI with encryption keys begin + - type: IntrinsicRadioReceiver + channels: + - Syndicate + - type: ContainerContainer + containers: + key_slots: !type:Container + - type: EncryptionKeyHolder + keySlots: 2 + #SS220 PAI with encryption keys end - type: Appearance - type: GenericVisualizer visuals: diff --git a/Resources/Prototypes/SS220/Entities/Objects/Fun/pai.yml b/Resources/Prototypes/SS220/Entities/Objects/Fun/pai.yml new file mode 100644 index 000000000000..0e701e56bf15 --- /dev/null +++ b/Resources/Prototypes/SS220/Entities/Objects/Fun/pai.yml @@ -0,0 +1,80 @@ +- type: entity + parent: BaseItem + id: EncryptionKeysPersonalAI + components: + - type: Instrument + allowPercussion: false + handheld: false + bank: 1 + program: 2 + - type: UserInterface + interfaces: + enum.InstrumentUiKey.Key: + type: InstrumentBoundUserInterface + requireInputValidation: false + enum.StationMapUiKey.Key: + type: StationMapBoundUserInterface + requireInputValidation: false + - type: Sprite + sprite: Objects/Fun/pai.rsi + layers: + - state: pai-base + - state: pai-off-overlay + shader: unshaded + map: ["screen"] + - type: Input + context: "human" + - type: PAI + - type: BlockMovement + - type: ToggleableGhostRole + examineTextMindPresent: pai-system-pai-installed + examineTextMindSearching: pai-system-still-searching + examineTextNoMind: pai-system-off + beginSearchingText: pai-system-searching + roleName: pai-system-role-name + roleDescription: pai-system-role-description + roleRules: ghost-role-information-familiar-rules + wipeVerbText: pai-system-wipe-device-verb-text + wipeVerbPopup: pai-system-wiped-device + stopSearchVerbText: pai-system-stop-searching-verb-text + stopSearchVerbPopup: pai-system-stopped-searching + - type: Examiner + - type: IntrinsicRadioReceiver + channels: + - Binary + - type: IntrinsicRadioTransmitter + channels: + - Binary + - type: DoAfter + - type: Actions + - type: TypingIndicator + proto: robot + - type: TTS # Corvax-TTS + voice: adventure_core + - type: Speech + speechVerb: Robotic + speechSounds: Pai + # This has to be installed because otherwise they're not "alive", + # so they can ghost and come back. + # Note that the personal AI never "dies". + - type: MobState + allowedStates: + - Alive + - type: Appearance + - type: GenericVisualizer + visuals: + enum.ToggleableGhostRoleVisuals.Status: + screen: + Off: { state: pai-off-overlay } + Searching: { state: pai-searching-overlay } + On: { state: pai-on-overlay } + - type: StationMap + - type: ContainerContainer + containers: + key_slots: !type:Container + - type: ContainerFill + containers: + key_slots: + - EncryptionKeyCommon + - type: EncryptionKeyHolder + keySlots: 2 diff --git a/Resources/migration.yml b/Resources/migration.yml index 3d83f5e53942..030c3ea1f7dc 100644 --- a/Resources/migration.yml +++ b/Resources/migration.yml @@ -501,4 +501,7 @@ Wristwatch: null HatBase: null # 2024-09-11 SS220-upstream-merge -ClothingBeltChefFilled: null \ No newline at end of file +ClothingBeltChefFilled: null + +# 2024-09-17 SS220 PAI with encryption keys +PersonalAI: EncryptionKeysPersonalAI