diff --git a/Content.Server/Radio/Components/RadioMicrophoneComponent.cs b/Content.Server/Radio/Components/RadioMicrophoneComponent.cs index 8b2b1c1a30..86c750762b 100644 --- a/Content.Server/Radio/Components/RadioMicrophoneComponent.cs +++ b/Content.Server/Radio/Components/RadioMicrophoneComponent.cs @@ -1,6 +1,7 @@ using Content.Server.Radio.EntitySystems; using Content.Shared.Radio; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List; namespace Content.Server.Radio.Components; @@ -15,10 +16,23 @@ public sealed class RadioMicrophoneComponent : Component [DataField("broadcastChannel", customTypeSerializer: typeof(PrototypeIdSerializer))] public string BroadcastChannel = "Common"; + [ViewVariables, DataField("supportedChannels", customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List? SupportedChannels; + [ViewVariables(VVAccess.ReadWrite)] [DataField("listenRange")] public int ListenRange = 4; [DataField("enabled")] public bool Enabled = false; + + [DataField("powerRequired")] + public bool PowerRequired = false; + + /// + /// Whether or not the speaker must have an + /// unobstructed path to the radio to speak + /// + [DataField("unobstructedRequired")] + public bool UnobstructedRequired = false; } diff --git a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs index 8d192c4de0..8ff99ac5d9 100644 --- a/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs +++ b/Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs @@ -1,11 +1,14 @@ using Content.Server.Chat.Systems; +using Content.Server.Interaction; using Content.Server.Popups; +using Content.Server.Power.EntitySystems; using Content.Server.Radio.Components; using Content.Server.Speech; using Content.Server.Speech.Components; using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Radio; +using Content.Shared.Verbs; using Robust.Shared.Player; using Robust.Shared.Prototypes; @@ -20,6 +23,7 @@ public sealed class RadioDeviceSystem : EntitySystem [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly RadioSystem _radio = default!; + [Dependency] private readonly InteractionSystem _interaction = default!; // Used to prevent a shitter from using a bunch of radios to spam chat. private HashSet<(string, EntityUid)> _recentlySent = new(); @@ -31,6 +35,8 @@ public sealed class RadioDeviceSystem : EntitySystem SubscribeLocalEvent(OnExamine); SubscribeLocalEvent(OnActivateMicrophone); SubscribeLocalEvent(OnListen); + SubscribeLocalEvent(OnAttemptListen); + SubscribeLocalEvent>(OnGetVerbs); SubscribeLocalEvent(OnSpeakerInit); SubscribeLocalEvent(OnActivateSpeaker); @@ -80,6 +86,9 @@ public sealed class RadioDeviceSystem : EntitySystem if (!Resolve(uid, ref component)) return; + if (component.PowerRequired && !this.IsPowered(uid, EntityManager)) + return; + component.Enabled = !component.Enabled; if (!quiet) @@ -95,6 +104,39 @@ public sealed class RadioDeviceSystem : EntitySystem RemCompDeferred(uid); } + private void OnGetVerbs(EntityUid uid, RadioMicrophoneComponent component, GetVerbsEvent args) + { + if (!args.CanAccess || !args.CanInteract || args.Hands == null) + return; + + if (component.SupportedChannels == null || component.SupportedChannels.Count <= 1) + return; + + if (component.PowerRequired && !this.IsPowered(uid, EntityManager)) + return; + + foreach (var channel in component.SupportedChannels) + { + var proto = _protoMan.Index(channel); + + var v = new Verb + { + Text = proto.LocalizedName, + Priority = 1, + Category = VerbCategory.ChannelSelect, + Disabled = component.BroadcastChannel == channel, + DoContactInteraction = true, + Act = () => + { + component.BroadcastChannel = channel; + _popup.PopupEntity(Loc.GetString("handheld-radio-component-channel-set", + ("channel", channel)), uid, Filter.Entities(args.User)); + } + }; + args.Verbs.Add(v); + } + } + public void ToggleRadioSpeaker(EntityUid uid, EntityUid user, bool quiet = false, RadioSpeakerComponent? component = null) { if (!Resolve(uid, ref component)) @@ -121,8 +163,9 @@ public sealed class RadioDeviceSystem : EntitySystem if (!args.IsInDetailsRange) return; - var freq = _protoMan.Index(component.BroadcastChannel).Frequency; - args.PushMarkup(Loc.GetString("handheld-radio-component-on-examine", ("frequency", freq))); + var proto = _protoMan.Index(component.BroadcastChannel); + args.PushMarkup(Loc.GetString("handheld-radio-component-on-examine", ("frequency", proto.Frequency))); + args.PushMarkup(Loc.GetString("handheld-radio-component-chennel-examine", ("channel", proto.LocalizedName))); } private void OnListen(EntityUid uid, RadioMicrophoneComponent component, ListenEvent args) @@ -134,6 +177,15 @@ public sealed class RadioDeviceSystem : EntitySystem _radio.SendRadioMessage(args.Source, args.Message, _protoMan.Index(component.BroadcastChannel)); } + private void OnAttemptListen(EntityUid uid, RadioMicrophoneComponent component, ListenAttemptEvent args) + { + if (component.PowerRequired && !this.IsPowered(uid, EntityManager) + || component.UnobstructedRequired && !_interaction.InRangeUnobstructed(args.Source, uid, 0)) + { + args.Cancel(); + } + } + private void OnReceiveRadio(EntityUid uid, RadioSpeakerComponent component, RadioReceiveEvent args) { var nameEv = new TransformSpeakerNameEvent(args.Source, Name(args.Source)); @@ -143,6 +195,6 @@ public sealed class RadioDeviceSystem : EntitySystem ("originalName", nameEv.Name)); var hideGlobalGhostChat = true; // log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios - _chat.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Speak, false, nameOverride: name, hideGlobalGhostChat:hideGlobalGhostChat, checkRadioPrefix: false); + _chat.TrySendInGameICMessage(uid, args.Message, InGameICChatType.Speak, false, nameOverride: name, hideGlobalGhostChat:hideGlobalGhostChat, checkRadioPrefix: false); } } diff --git a/Content.Shared/Verbs/VerbCategory.cs b/Content.Shared/Verbs/VerbCategory.cs index 51c26c91cf..1f63f3d106 100644 --- a/Content.Shared/Verbs/VerbCategory.cs +++ b/Content.Shared/Verbs/VerbCategory.cs @@ -76,8 +76,10 @@ namespace Content.Shared.Verbs public static readonly VerbCategory InstrumentStyle = new("verb-categories-instrument-style", null); + public static readonly VerbCategory ChannelSelect = new("verb-categories-channel-select", null); + public static readonly VerbCategory SetSensor = new("verb-categories-set-sensor", null); - + public static readonly VerbCategory Lever = new("verb-categories-lever", null); } } diff --git a/Resources/Locale/en-US/radio/components/handheld-radio-component.ftl b/Resources/Locale/en-US/radio/components/handheld-radio-component.ftl index ba7506ca64..39bbf8f968 100644 --- a/Resources/Locale/en-US/radio/components/handheld-radio-component.ftl +++ b/Resources/Locale/en-US/radio/components/handheld-radio-component.ftl @@ -1,4 +1,6 @@ handheld-radio-component-on-use = The radio is now {$radioState}. handheld-radio-component-on-examine = It's set to broadcast over the {$frequency} frequency. handheld-radio-component-on-state = on -handheld-radio-component-off-state = off \ No newline at end of file +handheld-radio-component-off-state = off +handheld-radio-component-channel-set = Channel set to {$channel} +handheld-radio-component-chennel-examine = The current channel is {$channel}. \ No newline at end of file diff --git a/Resources/Locale/en-US/verbs/verb-system.ftl b/Resources/Locale/en-US/verbs/verb-system.ftl index 6ed0b94858..32a3f5d521 100644 --- a/Resources/Locale/en-US/verbs/verb-system.ftl +++ b/Resources/Locale/en-US/verbs/verb-system.ftl @@ -22,6 +22,7 @@ verb-categories-tricks = Tricks verb-categories-transfer = Set Transfer Amount verb-categories-split = Split verb-categories-instrument-style = Instrument Style +verb-categories-channel-select = Channels verb-categories-set-sensor = Sensor verb-categories-timer = Set Delay verb-categories-lever = Lever diff --git a/Resources/Prototypes/Catalog/Research/technologies.yml b/Resources/Prototypes/Catalog/Research/technologies.yml index 50453e5111..ebd307aa1c 100644 --- a/Resources/Prototypes/Catalog/Research/technologies.yml +++ b/Resources/Prototypes/Catalog/Research/technologies.yml @@ -292,6 +292,7 @@ requiredTechnologies: - BasicResearch unlockedRecipes: + - IntercomElectronics - ConveyorBeltAssembly - FlashlightLantern - FireExtinguisher diff --git a/Resources/Prototypes/Entities/Objects/Devices/Electronics/intercom.yml b/Resources/Prototypes/Entities/Objects/Devices/Electronics/intercom.yml new file mode 100644 index 0000000000..3bd3be9bbe --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Devices/Electronics/intercom.yml @@ -0,0 +1,14 @@ +- type: entity + id: IntercomElectronics + parent: BaseElectronics + name: intercom electronics + description: An electronics board used in intercoms + components: + - type: Sprite + sprite: Objects/Misc/module.rsi + state: id_mod + - type: Tag + tags: + - DroneUsable + - IntercomElectronics + diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml index 885bd03f33..3dddd25c35 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml @@ -205,6 +205,7 @@ - APCElectronics - AirAlarmElectronics - FireAlarmElectronics + - IntercomElectronics - MailingUnitElectronics - Bucket - MopItem @@ -256,6 +257,7 @@ - AirAlarmElectronics - FireAlarmElectronics - MailingUnitElectronics + - IntercomElectronics - SMESMachineCircuitboard - SubstationMachineCircuitboard - ThermomachineFreezerMachineCircuitBoard diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml new file mode 100644 index 0000000000..7920a9559c --- /dev/null +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/intercom.yml @@ -0,0 +1,189 @@ +- type: entity + id: Intercom + name: intercom + description: An intercom. For when the station just needs to know something. + components: + - type: WallMount + - type: ApcPowerReceiver + - type: Electrified + enabled: false + usesApcPower: true + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + - type: ExtensionCableReceiver + - type: Clickable + - type: InteractionOutline + - type: Appearance + - type: Sprite + sprite: Structures/Wallmounts/intercom.rsi + layers: + - state: base + - state: unshaded + map: ["enum.PowerDeviceVisualLayers.Powered"] + shader: unshaded + - type: Transform + noRot: true + anchored: true + - type: Wires + BoardName: "Intercom" + LayoutId: Intercom + - type: Construction + graph: Intercom + node: intercom + - type: Damageable + damageContainer: Inorganic + damageModifierSet: Metallic + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 100 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - !type:PlaySoundBehavior + sound: + path: /Audio/Effects/metalbreak.ogg + - type: GenericVisualizer + visuals: + enum.PowerDeviceVisuals.Powered: + enum.PowerDeviceVisualLayers.Powered: + True: { visible: true } + False: { visible: false } + placement: + mode: SnapgridCenter + snap: + - Wallmount + +- type: entity + id: IntercomAssesmbly + name: intercom assembly + description: An intercom. It doesn't seem very helpful right now. + components: + - type: WallMount + - type: Clickable + - type: InteractionOutline + - type: Sprite + sprite: Structures/Wallmounts/intercom.rsi + state: build + - type: Construction + graph: Intercom + node: assembly + - type: Transform + anchored: true + placement: + mode: SnapgridCenter + snap: + - Wallmount + +- type: entity + id: IntercomCommand + parent: Intercom + suffix: Command + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Command + +- type: entity + id: IntercomEngineering + parent: Intercom + suffix: Engineering + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Engineering + +- type: entity + id: IntercomMedical + parent: Intercom + suffix: Medical + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Medical + +- type: entity + id: IntercomScience + parent: Intercom + suffix: Science + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Science + +- type: entity + id: IntercomSecurity + parent: Intercom + suffix: Security + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Security + +- type: entity + id: IntercomService + parent: Intercom + suffix: Service + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Service + +- type: entity + id: IntercomSupply + parent: Intercom + suffix: Supply + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Supply + +- type: entity + id: IntercomAll + parent: Intercom + suffix: All + components: + - type: RadioMicrophone + powerRequired: true + unobstructedRequired: true + listenRange: 2 + supportedChannels: + - Common + - Command + - Engineering + - Medical + - Science + - Security + - Service + - Supply \ No newline at end of file diff --git a/Resources/Prototypes/Recipes/Construction/Graphs/utilities/intercom.yml b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/intercom.yml new file mode 100644 index 0000000000..a1e8e679ce --- /dev/null +++ b/Resources/Prototypes/Recipes/Construction/Graphs/utilities/intercom.yml @@ -0,0 +1,72 @@ +- type: constructionGraph + id: Intercom + start: start + graph: + - node: start + edges: + - to: assembly + steps: + - material: Steel + amount: 2 + doAfter: 2.0 + + - node: assembly + entity: IntercomAssesmbly + edges: + - to: wired + steps: + - material: Cable + amount: 2 + doAfter: 1 + - to: start + completed: + - !type:GivePrototype + prototype: SheetSteel1 + amount: 2 + - !type:DeleteEntity {} + steps: + - tool: Welding + doAfter: 2 + + - node: wired + entity: IntercomAssesmbly + edges: + - to: electronics + steps: + - tag: IntercomElectronics + store: board + name: "intercom electronics" + icon: + sprite: "Objects/Misc/module.rsi" + state: "id_mod" + doAfter: 1 + - to: assembly + completed: + - !type:GivePrototype + prototype: CableApcStack1 + amount: 2 + steps: + - tool: Cutting + doAfter: 1 + + - node: electronics + edges: + - to: intercom + steps: + - tool: Screwing + doAfter: 2 + + - node: intercom + entity: Intercom #TODO: make this work with encryption keys + edges: + - to: wired + conditions: + - !type:AllWiresCut {} + - !type:WirePanel {} + - !type:ContainerNotEmpty + container: board + completed: + - !type:EmptyAllContainers {} + steps: + - tool: Prying + doAfter: 1 diff --git a/Resources/Prototypes/Recipes/Construction/utilities.yml b/Resources/Prototypes/Recipes/Construction/utilities.yml index af80f520e4..7ecbcaaed1 100644 --- a/Resources/Prototypes/Recipes/Construction/utilities.yml +++ b/Resources/Prototypes/Recipes/Construction/utilities.yml @@ -580,3 +580,22 @@ state: off conditions: - !type:TileNotBlocked {} + +# INTERCOM +- type: construction + name: intercom + id: IntercomAssesmbly + graph: Intercom + startNode: start + targetNode: intercom + category: construction-category-structures + description: An intercom. For when the station just needs to know something. + icon: + sprite: Structures/Wallmounts/intercom.rsi + state: base + placementMode: SnapgridCenter + objectType: Structure + canRotate: true + canBuildInImpassable: true + conditions: + - !type:WallmountCondition {} \ No newline at end of file diff --git a/Resources/Prototypes/Recipes/Lathes/electronics.yml b/Resources/Prototypes/Recipes/Lathes/electronics.yml index 2cef6729fe..d02671274b 100644 --- a/Resources/Prototypes/Recipes/Lathes/electronics.yml +++ b/Resources/Prototypes/Recipes/Lathes/electronics.yml @@ -34,6 +34,15 @@ Steel: 50 Plastic: 50 +- type: latheRecipe + id: IntercomElectronics + icon: { sprite: Objects/Misc/module.rsi, state: id_mod } + result: IntercomElectronics + completetime: 2 + materials: + Steel: 50 + Plastic: 50 + - type: latheRecipe id: FireAlarmElectronics icon: { sprite: Objects/Misc/module.rsi, state: door_electronics } diff --git a/Resources/Prototypes/tags.yml b/Resources/Prototypes/tags.yml index 5fda8238c0..663f7855d9 100644 --- a/Resources/Prototypes/tags.yml +++ b/Resources/Prototypes/tags.yml @@ -291,6 +291,9 @@ - type: Tag id: Ingot +- type: Tag + id: IntercomElectronics + - type: Tag id: JawsOfLife diff --git a/Resources/Textures/Structures/Wallmounts/intercom.rsi/base.png b/Resources/Textures/Structures/Wallmounts/intercom.rsi/base.png new file mode 100644 index 0000000000..787af3f538 Binary files /dev/null and b/Resources/Textures/Structures/Wallmounts/intercom.rsi/base.png differ diff --git a/Resources/Textures/Structures/Wallmounts/intercom.rsi/build.png b/Resources/Textures/Structures/Wallmounts/intercom.rsi/build.png new file mode 100644 index 0000000000..cfd5d5fffa Binary files /dev/null and b/Resources/Textures/Structures/Wallmounts/intercom.rsi/build.png differ diff --git a/Resources/Textures/Structures/Wallmounts/intercom.rsi/meta.json b/Resources/Textures/Structures/Wallmounts/intercom.rsi/meta.json new file mode 100644 index 0000000000..44babf5bfb --- /dev/null +++ b/Resources/Textures/Structures/Wallmounts/intercom.rsi/meta.json @@ -0,0 +1,23 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 32 + }, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from /tg/station at https://github.com/tgstation/tgstation/commit/6bfe3b2e4fcbcdac9159dc4f0327a82ddf05ba7bi", + "states": [ + { + "name": "base", + "directions": 4 + }, + { + "name": "build", + "directions": 4 + }, + { + "name": "unshaded", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Structures/Wallmounts/intercom.rsi/unshaded.png b/Resources/Textures/Structures/Wallmounts/intercom.rsi/unshaded.png new file mode 100644 index 0000000000..e50e20bbed Binary files /dev/null and b/Resources/Textures/Structures/Wallmounts/intercom.rsi/unshaded.png differ