Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
1af5054
Why cherrypick stuff
Huaqas Jul 24, 2026
0b7a115
Add Radio Host Job
Huaqas Jul 24, 2026
45e8179
EditorConfig Check Fixes
Huaqas Jul 24, 2026
c90ce87
Rename _Goob to _Goobstation
Huaqas Jul 24, 2026
f05b1f0
Fix Linter
Huaqas Jul 24, 2026
8e632f0
Rabbit quick wins.
Huaqas Jul 24, 2026
0ae1e88
Update discs.yml
Huaqas Jul 24, 2026
63856b8
Delete round-end-summary-window.ftl
Huaqas Jul 24, 2026
abff131
De-hardcode some shit
Huaqas Jul 24, 2026
3165db6
Missed a Delete
Huaqas Jul 24, 2026
acf3a2d
Fix some formatting.
Huaqas Jul 24, 2026
41a6a67
Remove LouderSpeech + Bug fix
Huaqas Jul 24, 2026
1b6373e
Fix Journalism
Huaqas Jul 24, 2026
af22e33
Add back louderspeech
Huaqas Jul 24, 2026
0d28c07
Fix forgotten vinyl sprite.
Huaqas Jul 25, 2026
b632f79
State based sprite copyright
Huaqas Jul 25, 2026
923a1aa
Update cds.yml
Huaqas Jul 25, 2026
fca2879
Update job-description.ftl
Huaqas Jul 25, 2026
9eded5f
Update StationRadioReceiverSystem.cs
Huaqas Jul 25, 2026
9f17065
Update meta.jsons
Huaqas Jul 25, 2026
204119b
Update meta.json
Huaqas Jul 25, 2026
bf5d16d
Update RadioSpeakerComponent.cs
Huaqas Jul 25, 2026
0c1105d
Update StationRadioReceiverSystem.cs
Huaqas Jul 25, 2026
fefb174
Adjust Threats
Huaqas Jul 25, 2026
4290762
Update meta.json
Huaqas Jul 25, 2026
91930e8
Merge remote-tracking branch 'upstream/starlight-dev' into Good-Radio…
Huaqas Aug 2, 2026
3be015c
Kill LouderSpeech for ParseRadioPrefix
Huaqas Aug 2, 2026
bcfa8b7
Remove Server check from VinylSummonRule
Huaqas Aug 2, 2026
454a4a6
When the server loses power, it stops the broadcast.
Huaqas Aug 2, 2026
33fce4f
Implement Resume Play
Huaqas Aug 2, 2026
6abfad5
Fix custom vinyl art not appearing.
Huaqas Aug 2, 2026
a6ed5ee
Add mapping extras
Huaqas Aug 2, 2026
367dce8
Remove Log.Info statements
Huaqas Aug 2, 2026
9ff696d
Turn low volume gain into its own datafield.
Huaqas Aug 2, 2026
581eeb2
Fix Integration Tests
Huaqas Aug 2, 2026
722550b
Final Rabbit Requests
Huaqas Aug 2, 2026
5ae71db
Fix alt verb context menu text being innaccurate
Huaqas Aug 2, 2026
30e9d5d
DAMN WHITE SPACES
Huaqas Aug 2, 2026
2f81867
Give Radio Host more Customization
Huaqas Aug 2, 2026
af25cb4
Merge branch 'starlight-dev' into Good-Radio-Host
Huaqas Aug 2, 2026
11bb44d
Apply Comments
Huaqas Aug 7, 2026
49ec3c1
Remove an old comment.
Huaqas Aug 7, 2026
0c2832a
Merge branch 'Good-Radio-Host' of https://github.com/Huaqas/star-stat…
Huaqas Aug 7, 2026
516e4a6
Merge remote-tracking branch 'upstream/starlight-dev' into Good-Radio…
Huaqas Aug 7, 2026
5a5fe99
Update base.yml
Huaqas Aug 7, 2026
06d14a9
Whitespaces.
Huaqas Aug 7, 2026
a5760c1
Fix Bug and Reorganise StationRadioReceiverSystem
Huaqas Aug 7, 2026
bbb7e01
Slow version of Numbers is now NT/Command Contra
Huaqas Aug 7, 2026
e8335c4
Add service comm key.
Huaqas Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 7 additions & 3 deletions Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Content.Shared.Speech;
using Content.Shared.Speech.Components;
using Robust.Shared.Prototypes;
using Content.Shared.Power.EntitySystems; // Goobstation - Radio Host

#region Starlight
using Content.Server._Starlight.Language;
Expand All @@ -32,6 +33,8 @@ public sealed partial class RadioDeviceSystem : SharedRadioDeviceSystem
[Dependency] private InteractionSystem _interaction = default!;
[Dependency] private SharedAppearanceSystem _appearance = default!;

[Dependency] private SharedPowerReceiverSystem _power = default!; // Goobstation - Radio Host

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

Expand Down Expand Up @@ -192,7 +195,7 @@ private void OnAttemptListen(EntityUid uid, RadioMicrophoneComponent component,

private void OnReceiveRadio(EntityUid uid, RadioSpeakerComponent component, ref RadioReceiveEvent args)
{
if (uid == args.RadioSource)
if (uid == args.RadioSource || component.PowerRequired && !_power.IsPowered(uid)) // Goobstation - Radio Host - Powered required
return;

var nameEv = new TransformSpeakerNameEvent(args.MessageSource, Name(args.MessageSource));
Expand All @@ -204,8 +207,9 @@ private void OnReceiveRadio(EntityUid uid, RadioSpeakerComponent component, ref

// log to chat so people can identity the speaker/source, but avoid clogging ghost chat if there are many radios
var message = args.OriginalChatMsg.Message; // Starlight-edit: The chat system will handle the rest and re-obfuscate if needed.
_chat.TrySendInGameICMessage(uid, message, InGameICChatType.Whisper, ChatTransmitRange.GhostRangeLimit,
nameOverride: name, checkRadioPrefix: false, languageOverride: args.Language); // Starlight
_chat.TrySendInGameICMessage(uid, message, InGameICChatType.Whisper, ChatTransmitRange.GhostRangeLimit, nameOverride: name,
checkRadioPrefix: component.ParseRadioPrefix, // Starlight - Radio Host, change LouderSpeech to ParseRadioPrefix
languageOverride: args.Language); // Starlight
}

private void OnIntercomEncryptionChannelsChanged(Entity<IntercomComponent> ent, ref EncryptionChannelsChangedEvent args)
Expand Down
230 changes: 230 additions & 0 deletions Content.Server/_Goobstation/StationRadio/VinylSummonRuleSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
using Content.Shared._Goobstation.StationRadio.Components; // Starlight - _Goob -> _Goobstation
using Content.Shared._Goobstation.StationRadio.Events; // Starlight - _Goob -> _Goobstation
using Content.Server.GameTicking;
using Content.Server.Station.Systems;
using Content.Shared.Communications;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Popups;
using Content.Shared.Power.EntitySystems;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using System.Linq;
using Content.Shared.Radio.Components;
using Content.Server.Chat.Systems;
using Content.Shared._Goobstation.StationRadio.Systems; // Starlight - Station Radio Check oved to StationRadioReceiverSystem

namespace Content.Server._Goobstation.StationRadio; // Starlight - _Goob -> _Goobstation

/// <summary>
/// System that handles spawning game rules when vinyl disks finish playing.
/// </summary>
public sealed partial class VinylSummonRuleSystem : EntitySystem
{
[Dependency] private IGameTiming _timing = default!;
[Dependency] private SharedAudioSystem _audio = default!;
[Dependency] private GameTicker _gameTicker = default!;
[Dependency] private SharedContainerSystem _containers = default!;
[Dependency] private IPrototypeManager _prototypeManager = default!;
[Dependency] private IRobustRandom _random = default!;
[Dependency] private StationSystem _stationSystem = default!;
[Dependency] private SharedPowerReceiverSystem _power = default!;
[Dependency] private ItemSlotsSystem _itemSlots = default!;
[Dependency] private SharedPopupSystem _popups = default!;
[Dependency] private ChatSystem _chat = default!;
[Dependency] private readonly StationRadioReceiverSystem _stationRadio = default!; // Starlight - Station Radio Check oved to StationRadioReceiverSystem

private record struct TrackingData(EntityUid VinylPlayerUid, TimeSpan EndTime);
private readonly Dictionary<EntityUid, TrackingData> _trackingVinyls = new();

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<VinylPlayerComponent, VinylInsertedEvent>(OnVinylInserted);
SubscribeLocalEvent<VinylPlayerComponent, VinylRemovedEvent>(OnVinylRemoved);
}

private void OnVinylInserted(EntityUid uid, VinylPlayerComponent player, ref VinylInsertedEvent args)
{
var playerUid = uid;
var vinylUid = args.Vinyl;

// Check if the inserted entity has the summon rule component / A song
if (!TryComp<VinylSummonRuleComponent>(vinylUid, out _)
|| !TryComp<VinylComponent>(vinylUid, out var vinylComp)
|| vinylComp.Song == null)
return;

void QueueSafeEject()
{
Timer.Spawn(0, () => EjectVinyl(playerUid, vinylUid));
}

// Check if vinyl player is on a station
if (_stationSystem.GetOwningStation(playerUid) == null)
{
_popups.PopupPredicted(Loc.GetString("vinyl-popout-no-station"), playerUid, null, PopupType.Medium);
QueueSafeEject();
return;
}

// Check if vinyl player is powered
if (!_power.IsPowered(playerUid))
{
_popups.PopupPredicted(Loc.GetString("vinyl-popout-no-power"), playerUid, null, PopupType.Medium);
QueueSafeEject();
return;
}

// Check if vinyl player is connected to the radio system
if (!_stationRadio.TryGetLinkedPoweredServer(playerUid, out _)) // Starlight - Station Radio Check oved to StationRadioReceiverSystem
{
_popups.PopupPredicted(Loc.GetString("vinyl-popout-no-radio-connection"), playerUid, null, PopupType.Medium);
QueueSafeEject();
return;
}

// Get the audio length
var resolved = _audio.ResolveSound(vinylComp.Song);
var audioLength = _audio.GetAudioLength(resolved);
var endTime = _timing.CurTime + audioLength;

// Track this vinyl with its player
_trackingVinyls[vinylUid] = new TrackingData(playerUid, endTime);
}

private void OnVinylRemoved(EntityUid uid, VinylPlayerComponent player, ref VinylRemovedEvent args)
{
// Stop tracking if the vinyl is removed
_trackingVinyls.Remove(args.Vinyl);
}

public override void Update(float frameTime)
{
base.Update(frameTime);

var currentTime = _timing.CurTime;

foreach (var (vinylUid, data) in _trackingVinyls.ToList())
{
// Check if the vinyl still exists
if (!Exists(vinylUid)
|| !Exists(data.VinylPlayerUid))
{
_trackingVinyls.Remove(vinylUid);
continue;
}

// Check if vinyl player is still on a station
if (_stationSystem.GetOwningStation(data.VinylPlayerUid) == null)
{
_trackingVinyls.Remove(vinylUid);
_popups.PopupPredicted(Loc.GetString("vinyl-popout-no-station"), data.VinylPlayerUid, null, PopupType.Medium);
EjectVinyl(data.VinylPlayerUid, vinylUid);
continue;
}

// Check if vinyl player is still powered
if (!_power.IsPowered(data.VinylPlayerUid))
{
_trackingVinyls.Remove(vinylUid);
_popups.PopupPredicted(Loc.GetString("vinyl-popout-no-power"), data.VinylPlayerUid, null, PopupType.Medium);
EjectVinyl(data.VinylPlayerUid, vinylUid);
continue;
}

// Check if vinyl player is still connected to the radio system
if (!_stationRadio.TryGetLinkedPoweredServer(data.VinylPlayerUid, out _)) // Starlight - Station Radio Check oved to StationRadioReceiverSystem
{
_trackingVinyls.Remove(vinylUid);
_popups.PopupPredicted(Loc.GetString("vinyl-popout-no-radio-connection"), data.VinylPlayerUid, null, PopupType.Medium);
EjectVinyl(data.VinylPlayerUid, vinylUid);
continue;
}

// Check if playback has finished
if (currentTime >= data.EndTime)
{
HandleVinylFinished(vinylUid);
_trackingVinyls.Remove(vinylUid);
}
}
}

private void EjectVinyl(EntityUid playerUid, EntityUid vinylUid)
{
if (!Exists(vinylUid)
|| !Exists(playerUid)
|| !TryComp<ItemSlotsComponent>(playerUid, out var itemSlots))
return;

// Find the slot containing the vinyl
foreach (var (_, slot) in itemSlots.Slots)
if (slot.Item == vinylUid)
{
_itemSlots.TryEject(playerUid, slot, null, out _);
return;
}
}

private void HandleVinylFinished(EntityUid vinylUid)
{
if (!TryComp<VinylSummonRuleComponent>(vinylUid, out var summonComp))
return;

// Resolve the game rule ID and get the threat prototype if available
var ruleId = ResolveGameRule(summonComp.GameRule, out var threat);

if (ruleId != null)
{
_gameTicker.StartGameRule(ruleId, out _);

// If we have a threat prototype with an announcement, send it
if (threat != null)
_chat.DispatchGlobalAnnouncement(Loc.GetString(threat.Announcement), playSound: true, colorOverride: Color.Red);
}

var vinylXform = Transform(vinylUid);
var vinylCoords = vinylXform.Coordinates;

// Remove from container
if (_containers.TryGetContainingContainer((vinylUid, vinylXform, null), out var container))
_containers.Remove(vinylUid, container);

// Play sound effect
_audio.PlayPvs(summonComp.BurnSound, vinylCoords, summonComp.BurnSoundParams); // Starlight - Dehardcode BurnSoundParams

// Spawn ash at the vinyl's location
Spawn(summonComp.AshPrototype, vinylCoords); // Starlight - Dehardcode ash prototype

// Delete the vinyl
QueueDel(vinylUid);
}

private string? ResolveGameRule(string gameRuleIdentifier, out NinjaHackingThreatPrototype? threat)
{
threat = null;

// Check if it's a weighted random pool
if (_prototypeManager.TryIndex<WeightedRandomPrototype>(gameRuleIdentifier, out var weightedPool))
{
// Pick a random threat ID from the weighted pool
var threatId = weightedPool.Pick(_random);

// Look up the threat prototype to get the actual game rule ID
if (_prototypeManager.TryIndex<NinjaHackingThreatPrototype>(threatId, out threat))
return threat.Rule;

return null;
}

// Assume it's a direct game rule entity ID
return gameRuleIdentifier;
}
}
19 changes: 18 additions & 1 deletion Content.Shared/Radio/Components/RadioSpeakerComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
using Content.Shared.Chat;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Content.Shared._Goobstation.StationRadio.Systems; // Starlight - Radio Host _Goob -> _Goobstation

namespace Content.Shared.Radio.Components;

/// <summary>
/// Listens for radio messages and relays them to local chat.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedRadioDeviceSystem))]
[Access(typeof(SharedRadioDeviceSystem), typeof(StationRadioReceiverSystem))] // Goobstation - Add StationRadioReceiverSystem access.
public sealed partial class RadioSpeakerComponent : Component
{
/// <summary>
Expand All @@ -24,4 +25,20 @@ public sealed partial class RadioSpeakerComponent : Component

[DataField, AutoNetworkedField]
public bool Enabled;

// Starlight - Radio Host LouderSpeech -> ParseRadioPrefix.
/// <summary>
/// Whether or not a message is parsed through the radio when when sent in local chat.
/// </summary>
[DataField, AutoNetworkedField]
public bool ParseRadioPrefix;
// Starlight - End

// Goobstation - Radio Host
/// <summary>
/// Does the radio need to be on a power grid to work?
/// </summary>
[DataField]
public bool PowerRequired;
// Goobstation - End
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Robust.Shared.GameStates;

namespace Content.Shared._Goobstation.StationRadio.Components; // Starlight - _Goob -> _Goobstation

[RegisterComponent, NetworkedComponent]
public sealed partial class RadioRigComponent : Component;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;

namespace Content.Shared._Goobstation.StationRadio.Components; // Starlight - _Goob -> _Goobstation

[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class StationRadioReceiverComponent : Component
{
/// <summary>
/// The sound entity being played
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? SoundEntity;

/// <summary>
/// Is the radio turned on
/// </summary>
[DataField, AutoNetworkedField]
public bool Active = true;

/// <summary>
/// Default audio params for the played audio.
/// </summary>
[DataField, AutoNetworkedField]
public AudioParams DefaultParams = AudioParams.Default.WithVolume(3.5f).WithMaxDistance(8f); // 8 is just the edge of the screen usually

// Moffstation - Add Low Volume mode
/// <summary>
/// Is Radio is playing at full or low volume.
/// </summary>
[DataField, AutoNetworkedField]
public bool LowVolume;
// Moffstation - End

// Starlight - Dehardcode LowVolume mode's gain.
/// <summary>
/// How quiet the low volume setting on a Station Radio is. Default is 10%.
/// </summary>
[DataField, AutoNetworkedField]
public float LowVolumeGain = 0.1f;
// Starlight - End
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Robust.Shared.GameStates;
using Robust.Shared.Audio; // Starlight - Add Station Radio Resume Play

namespace Content.Shared._Goobstation.StationRadio.Components; // Starlight - _Goob -> _Goobstation

[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class StationRadioServerComponent : Component
// Starlight - Add the ability for Station Radios to resume play.
{
/// <summary>
/// The song currently being broadcasted.
/// Null if nothing is playing.
/// </summary>
[DataField, AutoNetworkedField]
public SoundPathSpecifier? CurrentSong;

/// <summary>
/// For determining where the sound should resume.
/// </summary>
[DataField, AutoNetworkedField]
public TimeSpan? PlaybackStartTime;
}
// Starlight - End
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Robust.Shared.Audio;
using Robust.Shared.GameStates;

namespace Content.Shared._Goobstation.StationRadio.Components; // Starlight - _Goob -> _Goobstation

[RegisterComponent, NetworkedComponent]
public sealed partial class VinylComponent : Component
{
/// <summary>
/// What song should be played when the vinyl is played
/// </summary>
[DataField] public SoundPathSpecifier? Song;
}
Loading
Loading