Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intercom buffs and fixes #29580

Merged
merged 4 commits into from
Jul 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Content.Client/Radio/EntitySystems/RadioDeviceSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Content.Client.Radio.Ui;
using Content.Shared.Radio;
using Content.Shared.Radio.Components;
using Robust.Client.GameObjects;

namespace Content.Client.Radio.EntitySystems;

public sealed class RadioDeviceSystem : EntitySystem
{
[Dependency] private readonly UserInterfaceSystem _ui = default!;

/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<IntercomComponent, AfterAutoHandleStateEvent>(OnAfterHandleState);
}

private void OnAfterHandleState(Entity<IntercomComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (_ui.TryGetOpenUi<IntercomBoundUserInterface>(ent.Owner, IntercomUiKey.Key, out var bui))
bui.Update(ent);
}
}
15 changes: 6 additions & 9 deletions Content.Client/Radio/Ui/IntercomBoundUserInterface.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Content.Shared.Radio;
using Content.Shared.Radio.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects;

namespace Content.Client.Radio.Ui;

Expand All @@ -19,7 +19,9 @@ protected override void Open()
{
base.Open();

_menu = new();
var comp = EntMan.GetComponent<IntercomComponent>(Owner);

_menu = new((Owner, comp));

_menu.OnMicPressed += enabled =>
{
Expand All @@ -46,13 +48,8 @@ protected override void Dispose(bool disposing)
_menu?.Close();
}

protected override void UpdateState(BoundUserInterfaceState state)
public void Update(Entity<IntercomComponent> ent)
{
base.UpdateState(state);

if (state is not IntercomBoundUIState msg)
return;

_menu?.Update(msg);
_menu?.Update(ent);
}
}
37 changes: 27 additions & 10 deletions Content.Client/Radio/Ui/IntercomMenu.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Radio;
using Content.Shared.Radio.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;

namespace Content.Client.Radio.Ui;

Expand All @@ -17,38 +18,54 @@ public sealed partial class IntercomMenu : FancyWindow

private readonly List<string> _channels = new();

public IntercomMenu()
public IntercomMenu(Entity<IntercomComponent> entity)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);

MicButton.OnPressed += args => OnMicPressed?.Invoke(args.Button.Pressed);
SpeakerButton.OnPressed += args => OnSpeakerPressed?.Invoke(args.Button.Pressed);

Update(entity);
}

public void Update(IntercomBoundUIState state)
public void Update(Entity<IntercomComponent> entity)
{
MicButton.Pressed = state.MicEnabled;
SpeakerButton.Pressed = state.SpeakerEnabled;
MicButton.Pressed = entity.Comp.MicrophoneEnabled;
SpeakerButton.Pressed = entity.Comp.SpeakerEnabled;

MicButton.Disabled = entity.Comp.SupportedChannels.Count == 0;
SpeakerButton.Disabled = entity.Comp.SupportedChannels.Count == 0;
ChannelOptions.Disabled = entity.Comp.SupportedChannels.Count == 0;

ChannelOptions.Clear();
_channels.Clear();
for (var i = 0; i < state.AvailableChannels.Count; i++)
for (var i = 0; i < entity.Comp.SupportedChannels.Count; i++)
{
var channel = state.AvailableChannels[i];
if (!_prototype.TryIndex<RadioChannelPrototype>(channel, out var prototype))
var channel = entity.Comp.SupportedChannels[i];
if (!_prototype.TryIndex(channel, out var prototype))
continue;

_channels.Add(channel);
ChannelOptions.AddItem(Loc.GetString(prototype.Name), i);

if (channel == state.SelectedChannel)
if (channel == entity.Comp.CurrentChannel)
ChannelOptions.Select(i);
}

if (entity.Comp.SupportedChannels.Count == 0)
{
ChannelOptions.AddItem(Loc.GetString("intercom-options-none"), 0);
ChannelOptions.Select(0);
}

ChannelOptions.OnItemSelected += args =>
{
if (!_channels.TryGetValue(args.Id, out var proto))
return;

ChannelOptions.SelectId(args.Id);
OnChannelSelected?.Invoke(_channels[args.Id]);
OnChannelSelected?.Invoke(proto);
};
}
}
Expand Down
84 changes: 47 additions & 37 deletions Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using Content.Server.Chat.Systems;
using Content.Server.Interaction;
using Content.Server.Popups;
Expand All @@ -6,13 +7,10 @@
using Content.Server.Radio.Components;
using Content.Server.Speech;
using Content.Server.Speech.Components;
using Content.Shared.UserInterface;
using Content.Shared.Chat;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Radio;
using Content.Shared.Radio.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;

namespace Content.Server.Radio.EntitySystems;
Expand All @@ -28,7 +26,6 @@ public sealed class RadioDeviceSystem : EntitySystem
[Dependency] private readonly RadioSystem _radio = default!;
[Dependency] private readonly InteractionSystem _interaction = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly UserInterfaceSystem _ui = default!;

// Used to prevent a shitter from using a bunch of radios to spam chat.
private HashSet<(string, EntityUid)> _recentlySent = new();
Expand All @@ -47,7 +44,7 @@ public override void Initialize()
SubscribeLocalEvent<RadioSpeakerComponent, ActivateInWorldEvent>(OnActivateSpeaker);
SubscribeLocalEvent<RadioSpeakerComponent, RadioReceiveEvent>(OnReceiveRadio);

SubscribeLocalEvent<IntercomComponent, BeforeActivatableUIOpenEvent>(OnBeforeIntercomUiOpen);
SubscribeLocalEvent<IntercomComponent, EncryptionChannelsChangedEvent>(OnIntercomEncryptionChannelsChanged);
SubscribeLocalEvent<IntercomComponent, ToggleIntercomMicMessage>(OnToggleIntercomMic);
SubscribeLocalEvent<IntercomComponent, ToggleIntercomSpeakerMessage>(OnToggleIntercomSpeaker);
SubscribeLocalEvent<IntercomComponent, SelectIntercomChannelMessage>(OnSelectIntercomChannel);
Expand Down Expand Up @@ -150,18 +147,18 @@ public void ToggleRadioSpeaker(EntityUid uid, EntityUid user, bool quiet = false
SetSpeakerEnabled(uid, user, !component.Enabled, quiet, component);
}

public void SetSpeakerEnabled(EntityUid uid, EntityUid user, bool enabled, bool quiet = false, RadioSpeakerComponent? component = null)
public void SetSpeakerEnabled(EntityUid uid, EntityUid? user, bool enabled, bool quiet = false, RadioSpeakerComponent? component = null)
{
if (!Resolve(uid, ref component))
return;

component.Enabled = enabled;

if (!quiet)
if (!quiet && user != null)
{
var state = Loc.GetString(component.Enabled ? "handheld-radio-component-on-state" : "handheld-radio-component-off-state");
var message = Loc.GetString("handheld-radio-component-on-use", ("radioState", state));
_popup.PopupEntity(message, user, user);
_popup.PopupEntity(message, user.Value, user.Value);
}

_appearance.SetData(uid, RadioDeviceVisuals.Speaker, component.Enabled);
Expand Down Expand Up @@ -213,61 +210,74 @@ private void OnReceiveRadio(EntityUid uid, RadioSpeakerComponent component, ref
var nameEv = new TransformSpeakerNameEvent(args.MessageSource, Name(args.MessageSource));
RaiseLocalEvent(args.MessageSource, nameEv);

var name = Loc.GetString("speech-name-relay", ("speaker", Name(uid)),
var name = Loc.GetString("speech-name-relay",
("speaker", Name(uid)),
("originalName", nameEv.Name));

// 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.Whisper, ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false);
}

private void OnBeforeIntercomUiOpen(EntityUid uid, IntercomComponent component, BeforeActivatableUIOpenEvent args)
private void OnIntercomEncryptionChannelsChanged(Entity<IntercomComponent> ent, ref EncryptionChannelsChangedEvent args)
{
UpdateIntercomUi(uid, component);
ent.Comp.SupportedChannels = args.Component.Channels.Select(p => new ProtoId<RadioChannelPrototype>(p)).ToList();

var channel = args.Component.DefaultChannel;
if (ent.Comp.CurrentChannel != null && ent.Comp.SupportedChannels.Contains(ent.Comp.CurrentChannel.Value))
channel = ent.Comp.CurrentChannel;

SetIntercomChannel(ent, channel);
}

private void OnToggleIntercomMic(EntityUid uid, IntercomComponent component, ToggleIntercomMicMessage args)
private void OnToggleIntercomMic(Entity<IntercomComponent> ent, ref ToggleIntercomMicMessage args)
{
if (component.RequiresPower && !this.IsPowered(uid, EntityManager))
if (ent.Comp.RequiresPower && !this.IsPowered(ent, EntityManager))
return;

SetMicrophoneEnabled(uid, args.Actor, args.Enabled, true);
UpdateIntercomUi(uid, component);
SetMicrophoneEnabled(ent, args.Actor, args.Enabled, true);
ent.Comp.MicrophoneEnabled = args.Enabled;
Dirty(ent);
}

private void OnToggleIntercomSpeaker(EntityUid uid, IntercomComponent component, ToggleIntercomSpeakerMessage args)
private void OnToggleIntercomSpeaker(Entity<IntercomComponent> ent, ref ToggleIntercomSpeakerMessage args)
{
if (component.RequiresPower && !this.IsPowered(uid, EntityManager))
if (ent.Comp.RequiresPower && !this.IsPowered(ent, EntityManager))
return;

SetSpeakerEnabled(uid, args.Actor, args.Enabled, true);
UpdateIntercomUi(uid, component);
SetSpeakerEnabled(ent, args.Actor, args.Enabled, true);
ent.Comp.SpeakerEnabled = args.Enabled;
Dirty(ent);
}

private void OnSelectIntercomChannel(EntityUid uid, IntercomComponent component, SelectIntercomChannelMessage args)
private void OnSelectIntercomChannel(Entity<IntercomComponent> ent, ref SelectIntercomChannelMessage args)
{
if (component.RequiresPower && !this.IsPowered(uid, EntityManager))
if (ent.Comp.RequiresPower && !this.IsPowered(ent, EntityManager))
return;

if (!_protoMan.TryIndex<RadioChannelPrototype>(args.Channel, out _) || !component.SupportedChannels.Contains(args.Channel))
if (!_protoMan.HasIndex<RadioChannelPrototype>(args.Channel) || !ent.Comp.SupportedChannels.Contains(args.Channel))
return;

if (TryComp<RadioMicrophoneComponent>(uid, out var mic))
mic.BroadcastChannel = args.Channel;
if (TryComp<RadioSpeakerComponent>(uid, out var speaker))
speaker.Channels = new(){ args.Channel };
UpdateIntercomUi(uid, component);
SetIntercomChannel(ent, args.Channel);
}

private void UpdateIntercomUi(EntityUid uid, IntercomComponent component)
private void SetIntercomChannel(Entity<IntercomComponent> ent, ProtoId<RadioChannelPrototype>? channel)
{
var micComp = CompOrNull<RadioMicrophoneComponent>(uid);
var speakerComp = CompOrNull<RadioSpeakerComponent>(uid);

var micEnabled = micComp?.Enabled ?? false;
var speakerEnabled = speakerComp?.Enabled ?? false;
var availableChannels = component.SupportedChannels;
var selectedChannel = micComp?.BroadcastChannel ?? SharedChatSystem.CommonChannel;
var state = new IntercomBoundUIState(micEnabled, speakerEnabled, availableChannels, selectedChannel);
_ui.SetUiState(uid, IntercomUiKey.Key, state);
ent.Comp.CurrentChannel = channel;

if (channel == null)
{
SetSpeakerEnabled(ent, null, false);
SetMicrophoneEnabled(ent, null, false);
ent.Comp.MicrophoneEnabled = false;
ent.Comp.SpeakerEnabled = false;
Dirty(ent);
return;
}

if (TryComp<RadioMicrophoneComponent>(ent, out var mic))
mic.BroadcastChannel = channel;
if (TryComp<RadioSpeakerComponent>(ent, out var speaker))
speaker.Channels = new(){ channel };
Dirty(ent);
}
}
9 changes: 6 additions & 3 deletions Content.Server/Radio/EntitySystems/RadioSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@ public sealed class RadioSystem : EntitySystem
// set used to prevent radio feedback loops.
private readonly HashSet<string> _messages = new();

private EntityQuery<TelecomExemptComponent> _exemptQuery;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<IntrinsicRadioReceiverComponent, RadioReceiveEvent>(OnIntrinsicReceive);
SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntitySpokeEvent>(OnIntrinsicSpeak);

_exemptQuery = GetEntityQuery<TelecomExemptComponent>();
}

private void OnIntrinsicSpeak(EntityUid uid, IntrinsicRadioTransmitterComponent component, EntitySpokeEvent args)
Expand Down Expand Up @@ -121,9 +125,8 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann

var sourceMapId = Transform(radioSource).MapID;
var hasActiveServer = HasActiveServer(sourceMapId, channel.ID);
var hasMicro = HasComp<RadioMicrophoneComponent>(radioSource);
var sourceServerExempt = _exemptQuery.HasComp(radioSource);

var speakerQuery = GetEntityQuery<RadioSpeakerComponent>();
var radioQuery = EntityQueryEnumerator<ActiveRadioComponent, TransformComponent>();
while (canSend && radioQuery.MoveNext(out var receiver, out var radio, out var transform))
{
Expand All @@ -138,7 +141,7 @@ public void SendRadioMessage(EntityUid messageSource, string message, RadioChann
continue;

// don't need telecom server for long range channels or handheld radios and intercoms
var needServer = !channel.LongRange && (!hasMicro || !speakerQuery.HasComponent(receiver));
var needServer = !channel.LongRange && !sourceServerExempt;
if (needServer && !hasActiveServer)
continue;

Expand Down
21 changes: 15 additions & 6 deletions Content.Shared/Radio/Components/IntercomComponent.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
using Robust.Shared.Prototypes;

namespace Content.Shared.Radio.Components;

/// <summary>
/// Handles intercom ui and is authoritative on the channels an intercom can access.
/// </summary>
[RegisterComponent, NetworkedComponent]
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
public sealed partial class IntercomComponent : Component
{
/// <summary>
/// Does this intercom require popwer to function
/// Does this intercom require power to function
/// </summary>
[DataField("requiresPower"), ViewVariables(VVAccess.ReadWrite)]
[DataField]
public bool RequiresPower = true;

[DataField, AutoNetworkedField]
public bool SpeakerEnabled;

[DataField, AutoNetworkedField]
public bool MicrophoneEnabled;

[DataField, AutoNetworkedField]
public ProtoId<RadioChannelPrototype>? CurrentChannel;

/// <summary>
/// The list of radio channel prototypes this intercom can choose between.
/// </summary>
[DataField("supportedChannels", customTypeSerializer: typeof(PrototypeIdListSerializer<RadioChannelPrototype>))]
public List<string> SupportedChannels = new();
[DataField, AutoNetworkedField]
public List<ProtoId<RadioChannelPrototype>> SupportedChannels = new();
}
9 changes: 9 additions & 0 deletions Content.Shared/Radio/Components/TelecomExemptComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Robust.Shared.GameStates;

namespace Content.Shared.Radio.Components;

/// <summary>
/// This is used for a radio that doesn't need a telecom server in order to broadcast.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class TelecomExemptComponent : Component;
Loading
Loading