Skip to content

Commit

Permalink
Handheld Frequencies (DeltaV-Station#1574)
Browse files Browse the repository at this point in the history
<!--
This is a semi-strict format, you can add/remove sections as needed but
the order/format should be kept the same
Remove these comments before submitting
-->

# Description

<!--
Explain this PR in as much detail as applicable

Some example prompts to consider:
How might this affect the game? The codebase?
What might be some alternatives to this?
How/Who does this benefit/hurt [the game/codebase]?
-->

Adds frequencies to handheld radios. Ports [this
PR](new-frontiers-14/frontier-station-14#1833)
with changes.


![image](https://github.com/user-attachments/assets/e39d8fce-7fa3-4e0a-918a-506b0446de35)

---

# Changelog

<!--
You can add an author after the `:cl:` to change the name that appears
in the changelog (ex: `:cl: Death`)
Leaving it blank will default to your GitHub display name
This includes all available types for the changelog
-->

:cl:
- add: Added handheld radio frequencies, accessible by pressing your
"Use Item" key while holding one.

---------

Co-authored-by: Whatstone <[email protected]>
  • Loading branch information
sleepyyapril and whatston3 authored Jan 17, 2025
1 parent b5403ba commit 19a350f
Show file tree
Hide file tree
Showing 11 changed files with 394 additions and 27 deletions.
62 changes: 62 additions & 0 deletions Content.Client/_NC/Radio/UI/HandheldRadioBoundUserInterface.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Content.Shared._NC.Radio;
using JetBrains.Annotations;
using Robust.Client.GameObjects;

namespace Content.Client._NC.Radio.UI;


[UsedImplicitly]
public sealed class HandheldRadioBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private HandheldRadioMenu? _menu;

public HandheldRadioBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{

}

protected override void Open()
{
base.Open();

_menu = new();

_menu.OnMicPressed += enabled =>
{
SendMessage(new ToggleHandheldRadioMicMessage(enabled));
};
_menu.OnSpeakerPressed += enabled =>
{
SendMessage(new ToggleHandheldRadioSpeakerMessage(enabled));
};
_menu.OnFrequencyChanged += frequency =>
{
if (int.TryParse(frequency.Trim(), out var intFreq) && intFreq > 0)
SendMessage(new SelectHandheldRadioFrequencyMessage(intFreq));
else
SendMessage(new SelectHandheldRadioFrequencyMessage(-1)); // Query the current frequency
};

_menu.OnClose += Close;
_menu.OpenCentered();
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_menu?.Close();
}

protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);

if (state is not HandheldRadioBoundUIState msg)
return;

_menu?.Update(msg);
}
}
29 changes: 29 additions & 0 deletions Content.Client/_NC/Radio/UI/HandheldRadioMenu.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc 'handheld-radio-menu-title'}"
MinSize="200 150"
SetSize="200 150">
<BoxContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True"
Margin="5 0 5 0">
<Control MinHeight="10"/>
<BoxContainer Orientation="Vertical" SeparationOverride="4" MinWidth="150">
<Label Name="CurrentTextFrequency" Text="{Loc 'handheld-radio-current-text-frequency'}" />
<LineEdit Name="FrequencyLineEdit" Text="1330"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" HorizontalAlignment="Right" Margin="5 0 5 5">
<Button Name="MicButton" ToggleMode="True" Text="{Loc 'handheld-radio-button-text-mic'}" StyleClasses="OpenRight" MinWidth="70"/>
<Button Name="SpeakerButton" ToggleMode="True" Text="{Loc 'handheld-radio-button-text-speaker'}" StyleClasses="OpenLeft" MinWidth="70"/>
</BoxContainer>
<BoxContainer Orientation="Vertical">
<PanelContainer StyleClasses="LowDivider" />
<BoxContainer Orientation="Horizontal" Margin="10 2 5 0" VerticalAlignment="Bottom">
<Label Text="{Loc 'handheld-radio-flavor-text-left'}" StyleClasses="WindowFooterText"
HorizontalAlignment="Left" HorizontalExpand="True" Margin="0 0 5 0" />
<TextureRect StyleClasses="NTLogoDark" Stretch="KeepAspectCentered"
VerticalAlignment="Center" HorizontalAlignment="Right" SetSize="19 19"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
55 changes: 55 additions & 0 deletions Content.Client/_NC/Radio/UI/HandheldRadioMenu.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Content.Client.UserInterface.Controls;
using Content.Shared._NC.Radio;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;

namespace Content.Client._NC.Radio.UI;
// TODO: Fix silly ui update issues
[GenerateTypedNameReferences]
public sealed partial class HandheldRadioMenu : FancyWindow
{

public event Action<bool>? OnMicPressed;
public event Action<bool>? OnSpeakerPressed;
public event Action<string>? OnFrequencyChanged;

private string _lastFrequency = "1330";

public HandheldRadioMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);

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

FrequencyLineEdit.OnTextEntered += e => OnFrequencyChanged?.Invoke(e.Text);
FrequencyLineEdit.OnTextChanged += ValidateText;
FrequencyLineEdit.OnFocusExit += e => OnFrequencyChanged?.Invoke(e.Text);
}

private void ValidateText(LineEdit.LineEditEventArgs args)
{
if (!int.TryParse(args.Text, out int frequency))
{
FrequencyLineEdit.SetText(_lastFrequency);
return;
}

_lastFrequency = args.Text;
}

public void Update(HandheldRadioBoundUIState state)
{
if (state.MicEnabled != MicButton.Pressed)
MicButton.Pressed = state.MicEnabled;

if (state.SpeakerEnabled != SpeakerButton.Pressed)
SpeakerButton.Pressed = state.SpeakerEnabled;

if (state.Frequency.ToString() != FrequencyLineEdit.Text)
FrequencyLineEdit.Text = state.Frequency.ToString();
}
}
7 changes: 7 additions & 0 deletions Content.Server/Radio/Components/RadioMicrophoneComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,11 @@ public sealed partial class RadioMicrophoneComponent : Component
/// </summary>
[DataField("unobstructedRequired")]
public bool UnobstructedRequired = false;

// Nuclear-14
/// <summary>
// The radio frequency on which the message will be transmitted
/// </summary>
[DataField]
public int Frequency = 1459; // Common channel frequency
}
129 changes: 110 additions & 19 deletions Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Linq;
using Content.Server.Chat.Systems;
using Content.Server.Interaction;
using Content.Server.Language;
using Content.Server.Popups;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
Expand All @@ -13,6 +14,11 @@
using Content.Shared.Radio;
using Content.Shared.Chat;
using Content.Shared.Radio.Components;
using Content.Shared.UserInterface; // Nuclear-14
using Content.Shared._NC.Radio; // Nuclear-14
using Robust.Server.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Player; // Nuclear-14
using Robust.Shared.Prototypes;

namespace Content.Server.Radio.EntitySystems;
Expand All @@ -28,10 +34,17 @@ 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!;
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly LanguageSystem _language = default!;

// Used to prevent a shitter from using a bunch of radios to spam chat.
private HashSet<(string, EntityUid)> _recentlySent = new();

// Frontier: minimum, maximum radio frequencies
private const int MinRadioFrequency = 1000;
private const int MaxRadioFrequency = 3000;

public override void Initialize()
{
base.Initialize();
Expand All @@ -50,6 +63,13 @@ public override void Initialize()
SubscribeLocalEvent<IntercomComponent, ToggleIntercomMicMessage>(OnToggleIntercomMic);
SubscribeLocalEvent<IntercomComponent, ToggleIntercomSpeakerMessage>(OnToggleIntercomSpeaker);
SubscribeLocalEvent<IntercomComponent, SelectIntercomChannelMessage>(OnSelectIntercomChannel);

SubscribeLocalEvent<RadioMicrophoneComponent, BeforeActivatableUIOpenEvent>(OnBeforeHandheldRadioUiOpen);
SubscribeLocalEvent<RadioMicrophoneComponent, ToggleHandheldRadioMicMessage>(OnToggleHandheldRadioMic);
SubscribeLocalEvent<RadioMicrophoneComponent, ToggleHandheldRadioSpeakerMessage>(OnToggleHandheldRadioSpeaker);
SubscribeLocalEvent<RadioMicrophoneComponent, SelectHandheldRadioFrequencyMessage>(OnChangeHandheldRadioFrequency);

SubscribeLocalEvent<IntercomComponent, MapInitEvent>(OnMapInit); // Frontier
}

public override void Update(float frameTime)
Expand Down Expand Up @@ -180,9 +200,14 @@ private void OnExamine(EntityUid uid, RadioMicrophoneComponent component, Examin

using (args.PushGroup(nameof(RadioMicrophoneComponent)))
{
args.PushMarkup(Loc.GetString("handheld-radio-component-on-examine", ("frequency", proto.Frequency)));
args.PushMarkup(Loc.GetString("handheld-radio-component-chennel-examine",
("channel", proto.LocalizedName)));
args.PushMarkup(Loc.GetString(
"handheld-radio-component-on-examine",
("frequency", component.Frequency),
("color", proto.Color.ToHex())));
args.PushMarkup(Loc.GetString(
"handheld-radio-component-channel-examine",
("channel", proto.LocalizedName),
("color", proto.Color.ToHex())));
}
}

Expand All @@ -192,32 +217,28 @@ private void OnListen(EntityUid uid, RadioMicrophoneComponent component, ListenE
return; // no feedback loops please.

if (_recentlySent.Add((args.Message, args.Source)))
_radio.SendRadioMessage(args.Source, args.Message, _protoMan.Index<RadioChannelPrototype>(component.BroadcastChannel), uid);
_radio.SendRadioMessage(args.Source, args.Message, _protoMan.Index<RadioChannelPrototype>(component.BroadcastChannel), uid, /*Nuclear-14-start*/ frequency: component.Frequency /*Nuclear-14-end*/);
}

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, ref RadioReceiveEvent args)
{
if (uid == args.RadioSource)
return;

var nameEv = new TransformSpeakerNameEvent(args.MessageSource, Name(args.MessageSource));
RaiseLocalEvent(args.MessageSource, nameEv);

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

// log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios
_chat.TrySendInGameICMessage(uid, args.OriginalChatMsg.Message, InGameICChatType.Whisper, ChatTransmitRange.GhostRangeLimit, nameOverride: name, checkRadioPrefix: false);
var parent = Transform(uid).ParentUid;
if (TryComp(parent, out ActorComponent? actor))
{
var canUnderstand = _language.CanUnderstand(parent, args.Language.ID);
var msg = new MsgChatMessage
{
Message = canUnderstand ? args.OriginalChatMsg : args.LanguageObfuscatedChatMsg
};
_netMan.ServerSendMessage(msg, actor.PlayerSession.Channel);
}
}

private void OnIntercomEncryptionChannelsChanged(Entity<IntercomComponent> ent, ref EncryptionChannelsChangedEvent args)
Expand Down Expand Up @@ -256,7 +277,7 @@ private void OnSelectIntercomChannel(Entity<IntercomComponent> ent, ref SelectIn
if (ent.Comp.RequiresPower && !this.IsPowered(ent, EntityManager))
return;

if (!_protoMan.HasIndex<RadioChannelPrototype>(args.Channel) || !ent.Comp.SupportedChannels.Contains(args.Channel))
if (!_protoMan.TryIndex<RadioChannelPrototype>(args.Channel, out var channel) || !ent.Comp.SupportedChannels.Contains(args.Channel)) // Nuclear-14: add channel
return;

SetIntercomChannel(ent, args.Channel);
Expand All @@ -277,9 +298,79 @@ private void SetIntercomChannel(Entity<IntercomComponent> ent, ProtoId<RadioChan
}

if (TryComp<RadioMicrophoneComponent>(ent, out var mic))
{
mic.BroadcastChannel = channel;
if(_protoMan.TryIndex<RadioChannelPrototype>(channel, out var channelProto)) // Frontier
mic.Frequency = _radio.GetFrequency(ent, channelProto); // Frontier
}
if (TryComp<RadioSpeakerComponent>(ent, out var speaker))
speaker.Channels = new(){ channel };
Dirty(ent);
}

// Nuclear-14-Start
#region Handheld Radio

private void OnBeforeHandheldRadioUiOpen(Entity<RadioMicrophoneComponent> microphone, ref BeforeActivatableUIOpenEvent args)
{
UpdateHandheldRadioUi(microphone);
}

private void OnToggleHandheldRadioMic(Entity<RadioMicrophoneComponent> microphone, ref ToggleHandheldRadioMicMessage args)
{
if (!args.Actor.Valid)
return;

SetMicrophoneEnabled(microphone, args.Actor, args.Enabled, true);
UpdateHandheldRadioUi(microphone);
}

private void OnToggleHandheldRadioSpeaker(Entity<RadioMicrophoneComponent> microphone, ref ToggleHandheldRadioSpeakerMessage args)
{
if (!args.Actor.Valid)
return;

SetSpeakerEnabled(microphone, args.Actor, args.Enabled, true);
UpdateHandheldRadioUi(microphone);
}

private void OnChangeHandheldRadioFrequency(Entity<RadioMicrophoneComponent> microphone, ref SelectHandheldRadioFrequencyMessage args)
{
if (!args.Actor.Valid)
return;

// Update frequency if valid and within range.
if (args.Frequency >= MinRadioFrequency && args.Frequency <= MaxRadioFrequency)
microphone.Comp.Frequency = args.Frequency;
// Update UI with current frequency.
UpdateHandheldRadioUi(microphone);
}

private void UpdateHandheldRadioUi(Entity<RadioMicrophoneComponent> radio)
{
var speakerComp = CompOrNull<RadioSpeakerComponent>(radio);
var frequency = radio.Comp.Frequency;

var micEnabled = radio.Comp.Enabled;
var speakerEnabled = speakerComp?.Enabled ?? false;
var state = new HandheldRadioBoundUIState(micEnabled, speakerEnabled, frequency);
if (TryComp<UserInterfaceComponent>(radio, out var uiComp))
_ui.SetUiState((radio.Owner, uiComp), HandheldRadioUiKey.Key, state); // Frontier: TrySetUiState<SetUiState
}

#endregion
// Nuclear-14-End

// Frontier: init intercom with map
private void OnMapInit(EntityUid uid, IntercomComponent ent, MapInitEvent args)
{
// Set initial frequency (must be done regardless of power/enabled)
if (ent.CurrentChannel != null &&
_protoMan.TryIndex(ent.CurrentChannel, out var channel) &&
TryComp(uid, out RadioMicrophoneComponent? mic))
{
mic.Frequency = channel.Frequency;
}
}
// End Frontier
}
Loading

0 comments on commit 19a350f

Please sign in to comment.