Intercoms [crappy radios] (#12898)

This commit is contained in:
Nemanja
2022-12-13 20:39:54 -05:00
committed by GitHub
parent a3c8d2e306
commit 00497177f8
17 changed files with 408 additions and 5 deletions

View File

@@ -1,6 +1,7 @@
using Content.Server.Radio.EntitySystems; using Content.Server.Radio.EntitySystems;
using Content.Shared.Radio; using Content.Shared.Radio;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Server.Radio.Components; namespace Content.Server.Radio.Components;
@@ -15,10 +16,23 @@ public sealed class RadioMicrophoneComponent : Component
[DataField("broadcastChannel", customTypeSerializer: typeof(PrototypeIdSerializer<RadioChannelPrototype>))] [DataField("broadcastChannel", customTypeSerializer: typeof(PrototypeIdSerializer<RadioChannelPrototype>))]
public string BroadcastChannel = "Common"; public string BroadcastChannel = "Common";
[ViewVariables, DataField("supportedChannels", customTypeSerializer: typeof(PrototypeIdListSerializer<RadioChannelPrototype>))]
public List<string>? SupportedChannels;
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
[DataField("listenRange")] [DataField("listenRange")]
public int ListenRange = 4; public int ListenRange = 4;
[DataField("enabled")] [DataField("enabled")]
public bool Enabled = false; public bool Enabled = false;
[DataField("powerRequired")]
public bool PowerRequired = false;
/// <summary>
/// Whether or not the speaker must have an
/// unobstructed path to the radio to speak
/// </summary>
[DataField("unobstructedRequired")]
public bool UnobstructedRequired = false;
} }

View File

@@ -1,11 +1,14 @@
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
using Content.Server.Interaction;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.Power.EntitySystems;
using Content.Server.Radio.Components; using Content.Server.Radio.Components;
using Content.Server.Speech; using Content.Server.Speech;
using Content.Server.Speech.Components; using Content.Server.Speech.Components;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Radio; using Content.Shared.Radio;
using Content.Shared.Verbs;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -20,6 +23,7 @@ public sealed class RadioDeviceSystem : EntitySystem
[Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly RadioSystem _radio = 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. // Used to prevent a shitter from using a bunch of radios to spam chat.
private HashSet<(string, EntityUid)> _recentlySent = new(); private HashSet<(string, EntityUid)> _recentlySent = new();
@@ -31,6 +35,8 @@ public sealed class RadioDeviceSystem : EntitySystem
SubscribeLocalEvent<RadioMicrophoneComponent, ExaminedEvent>(OnExamine); SubscribeLocalEvent<RadioMicrophoneComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<RadioMicrophoneComponent, ActivateInWorldEvent>(OnActivateMicrophone); SubscribeLocalEvent<RadioMicrophoneComponent, ActivateInWorldEvent>(OnActivateMicrophone);
SubscribeLocalEvent<RadioMicrophoneComponent, ListenEvent>(OnListen); SubscribeLocalEvent<RadioMicrophoneComponent, ListenEvent>(OnListen);
SubscribeLocalEvent<RadioMicrophoneComponent, ListenAttemptEvent>(OnAttemptListen);
SubscribeLocalEvent<RadioMicrophoneComponent, GetVerbsEvent<Verb>>(OnGetVerbs);
SubscribeLocalEvent<RadioSpeakerComponent, ComponentInit>(OnSpeakerInit); SubscribeLocalEvent<RadioSpeakerComponent, ComponentInit>(OnSpeakerInit);
SubscribeLocalEvent<RadioSpeakerComponent, ActivateInWorldEvent>(OnActivateSpeaker); SubscribeLocalEvent<RadioSpeakerComponent, ActivateInWorldEvent>(OnActivateSpeaker);
@@ -80,6 +86,9 @@ public sealed class RadioDeviceSystem : EntitySystem
if (!Resolve(uid, ref component)) if (!Resolve(uid, ref component))
return; return;
if (component.PowerRequired && !this.IsPowered(uid, EntityManager))
return;
component.Enabled = !component.Enabled; component.Enabled = !component.Enabled;
if (!quiet) if (!quiet)
@@ -95,6 +104,39 @@ public sealed class RadioDeviceSystem : EntitySystem
RemCompDeferred<ActiveListenerComponent>(uid); RemCompDeferred<ActiveListenerComponent>(uid);
} }
private void OnGetVerbs(EntityUid uid, RadioMicrophoneComponent component, GetVerbsEvent<Verb> 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<RadioChannelPrototype>(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) public void ToggleRadioSpeaker(EntityUid uid, EntityUid user, bool quiet = false, RadioSpeakerComponent? component = null)
{ {
if (!Resolve(uid, ref component)) if (!Resolve(uid, ref component))
@@ -121,8 +163,9 @@ public sealed class RadioDeviceSystem : EntitySystem
if (!args.IsInDetailsRange) if (!args.IsInDetailsRange)
return; return;
var freq = _protoMan.Index<RadioChannelPrototype>(component.BroadcastChannel).Frequency; var proto = _protoMan.Index<RadioChannelPrototype>(component.BroadcastChannel);
args.PushMarkup(Loc.GetString("handheld-radio-component-on-examine", ("frequency", freq))); 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) 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<RadioChannelPrototype>(component.BroadcastChannel)); _radio.SendRadioMessage(args.Source, args.Message, _protoMan.Index<RadioChannelPrototype>(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) private void OnReceiveRadio(EntityUid uid, RadioSpeakerComponent component, RadioReceiveEvent args)
{ {
var nameEv = new TransformSpeakerNameEvent(args.Source, Name(args.Source)); var nameEv = new TransformSpeakerNameEvent(args.Source, Name(args.Source));
@@ -143,6 +195,6 @@ public sealed class RadioDeviceSystem : EntitySystem
("originalName", nameEv.Name)); ("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 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);
} }
} }

View File

@@ -76,8 +76,10 @@ namespace Content.Shared.Verbs
public static readonly VerbCategory InstrumentStyle = public static readonly VerbCategory InstrumentStyle =
new("verb-categories-instrument-style", null); 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 SetSensor = new("verb-categories-set-sensor", null);
public static readonly VerbCategory Lever = new("verb-categories-lever", null); public static readonly VerbCategory Lever = new("verb-categories-lever", null);
} }
} }

View File

@@ -1,4 +1,6 @@
handheld-radio-component-on-use = The radio is now {$radioState}. 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-examine = It's set to broadcast over the {$frequency} frequency.
handheld-radio-component-on-state = on handheld-radio-component-on-state = on
handheld-radio-component-off-state = off 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}.

View File

@@ -22,6 +22,7 @@ verb-categories-tricks = Tricks
verb-categories-transfer = Set Transfer Amount verb-categories-transfer = Set Transfer Amount
verb-categories-split = Split verb-categories-split = Split
verb-categories-instrument-style = Instrument Style verb-categories-instrument-style = Instrument Style
verb-categories-channel-select = Channels
verb-categories-set-sensor = Sensor verb-categories-set-sensor = Sensor
verb-categories-timer = Set Delay verb-categories-timer = Set Delay
verb-categories-lever = Lever verb-categories-lever = Lever

View File

@@ -292,6 +292,7 @@
requiredTechnologies: requiredTechnologies:
- BasicResearch - BasicResearch
unlockedRecipes: unlockedRecipes:
- IntercomElectronics
- ConveyorBeltAssembly - ConveyorBeltAssembly
- FlashlightLantern - FlashlightLantern
- FireExtinguisher - FireExtinguisher

View File

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

View File

@@ -205,6 +205,7 @@
- APCElectronics - APCElectronics
- AirAlarmElectronics - AirAlarmElectronics
- FireAlarmElectronics - FireAlarmElectronics
- IntercomElectronics
- MailingUnitElectronics - MailingUnitElectronics
- Bucket - Bucket
- MopItem - MopItem
@@ -256,6 +257,7 @@
- AirAlarmElectronics - AirAlarmElectronics
- FireAlarmElectronics - FireAlarmElectronics
- MailingUnitElectronics - MailingUnitElectronics
- IntercomElectronics
- SMESMachineCircuitboard - SMESMachineCircuitboard
- SubstationMachineCircuitboard - SubstationMachineCircuitboard
- ThermomachineFreezerMachineCircuitBoard - ThermomachineFreezerMachineCircuitBoard

View File

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

View File

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

View File

@@ -580,3 +580,22 @@
state: off state: off
conditions: conditions:
- !type:TileNotBlocked {} - !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 {}

View File

@@ -34,6 +34,15 @@
Steel: 50 Steel: 50
Plastic: 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 - type: latheRecipe
id: FireAlarmElectronics id: FireAlarmElectronics
icon: { sprite: Objects/Misc/module.rsi, state: door_electronics } icon: { sprite: Objects/Misc/module.rsi, state: door_electronics }

View File

@@ -291,6 +291,9 @@
- type: Tag - type: Tag
id: Ingot id: Ingot
- type: Tag
id: IntercomElectronics
- type: Tag - type: Tag
id: JawsOfLife id: JawsOfLife

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B