Skip to content

Commit 6bfae8e

Browse files
authored
Merge branch 'starlight-dev' into air-alarm-variants
2 parents be8040c + 1c47dc0 commit 6bfae8e

332 files changed

Lines changed: 264489 additions & 424 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Content.IntegrationTests/Tests/Power/StationPowerTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public sealed class StationPowerTests : GameTest
4949
"StarlightKiloton",
5050
"StarlightLagan",
5151
"StarlightLobster",
52+
"StarlightNovoLobster",
5253
"StarlightManor",
5354
"StarlightLeth",
5455
"StarlightMing",

Content.IntegrationTests/Tests/_Starlight/Power/GridPowerTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public sealed class GridPowerTests : GameTest
6060
new("/Maps/_Starlight/Shuttles/cargo_prism.yml"),
6161
new("/Maps/_Starlight/Shuttles/cargo_silica.yml"),
6262
new("/Maps/_Starlight/Shuttles/cargo_syndicate.yml"),
63+
new("/Maps/_Starlight/Shuttles/cargo_novolobster.yml"),
6364

6465
// Evac
6566
new("/Maps/_Starlight/Shuttles/emergency_cluster.yml"),

Content.Server/Radio/EntitySystems/RadioDeviceSystem.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Content.Shared.Speech;
1414
using Content.Shared.Speech.Components;
1515
using Robust.Shared.Prototypes;
16+
using Content.Shared.Power.EntitySystems; // Goobstation - Radio Host
1617

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

36+
[Dependency] private SharedPowerReceiverSystem _power = default!; // Goobstation - Radio Host
37+
3538
// Used to prevent a shitter from using a bunch of radios to spam chat.
3639
private HashSet<(string, EntityUid, string)> _recentlySent = new(); // Starlight edit
3740

@@ -192,7 +195,7 @@ private void OnAttemptListen(EntityUid uid, RadioMicrophoneComponent component,
192195

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

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

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

211215
private void OnIntercomEncryptionChannelsChanged(Entity<IntercomComponent> ent, ref EncryptionChannelsChangedEvent args)
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
using Content.Shared._Goobstation.StationRadio.Components; // Starlight - _Goob -> _Goobstation
2+
using Content.Shared._Goobstation.StationRadio.Events; // Starlight - _Goob -> _Goobstation
3+
using Content.Server.GameTicking;
4+
using Content.Server.Station.Systems;
5+
using Content.Shared.Communications;
6+
using Content.Shared.Containers.ItemSlots;
7+
using Content.Shared.Popups;
8+
using Content.Shared.Power.EntitySystems;
9+
using Content.Shared.Random;
10+
using Content.Shared.Random.Helpers;
11+
using Robust.Shared.Audio;
12+
using Robust.Shared.Audio.Systems;
13+
using Robust.Shared.Containers;
14+
using Robust.Shared.Prototypes;
15+
using Robust.Shared.Random;
16+
using Robust.Shared.Timing;
17+
using System.Linq;
18+
using Content.Shared.Radio.Components;
19+
using Content.Server.Chat.Systems;
20+
using Content.Shared._Goobstation.StationRadio.Systems; // Starlight - Station Radio Check oved to StationRadioReceiverSystem
21+
22+
namespace Content.Server._Goobstation.StationRadio; // Starlight - _Goob -> _Goobstation
23+
24+
/// <summary>
25+
/// System that handles spawning game rules when vinyl disks finish playing.
26+
/// </summary>
27+
public sealed partial class VinylSummonRuleSystem : EntitySystem
28+
{
29+
[Dependency] private IGameTiming _timing = default!;
30+
[Dependency] private SharedAudioSystem _audio = default!;
31+
[Dependency] private GameTicker _gameTicker = default!;
32+
[Dependency] private SharedContainerSystem _containers = default!;
33+
[Dependency] private IPrototypeManager _prototypeManager = default!;
34+
[Dependency] private IRobustRandom _random = default!;
35+
[Dependency] private StationSystem _stationSystem = default!;
36+
[Dependency] private SharedPowerReceiverSystem _power = default!;
37+
[Dependency] private ItemSlotsSystem _itemSlots = default!;
38+
[Dependency] private SharedPopupSystem _popups = default!;
39+
[Dependency] private ChatSystem _chat = default!;
40+
[Dependency] private readonly StationRadioReceiverSystem _stationRadio = default!; // Starlight - Station Radio Check oved to StationRadioReceiverSystem
41+
42+
private record struct TrackingData(EntityUid VinylPlayerUid, TimeSpan EndTime);
43+
private readonly Dictionary<EntityUid, TrackingData> _trackingVinyls = new();
44+
45+
public override void Initialize()
46+
{
47+
base.Initialize();
48+
49+
SubscribeLocalEvent<VinylPlayerComponent, VinylInsertedEvent>(OnVinylInserted);
50+
SubscribeLocalEvent<VinylPlayerComponent, VinylRemovedEvent>(OnVinylRemoved);
51+
}
52+
53+
private void OnVinylInserted(EntityUid uid, VinylPlayerComponent player, ref VinylInsertedEvent args)
54+
{
55+
var playerUid = uid;
56+
var vinylUid = args.Vinyl;
57+
58+
// Check if the inserted entity has the summon rule component / A song
59+
if (!TryComp<VinylSummonRuleComponent>(vinylUid, out _)
60+
|| !TryComp<VinylComponent>(vinylUid, out var vinylComp)
61+
|| vinylComp.Song == null)
62+
return;
63+
64+
void QueueSafeEject()
65+
{
66+
Timer.Spawn(0, () => EjectVinyl(playerUid, vinylUid));
67+
}
68+
69+
// Check if vinyl player is on a station
70+
if (_stationSystem.GetOwningStation(playerUid) == null)
71+
{
72+
_popups.PopupPredicted(Loc.GetString("vinyl-popout-no-station"), playerUid, null, PopupType.Medium);
73+
QueueSafeEject();
74+
return;
75+
}
76+
77+
// Check if vinyl player is powered
78+
if (!_power.IsPowered(playerUid))
79+
{
80+
_popups.PopupPredicted(Loc.GetString("vinyl-popout-no-power"), playerUid, null, PopupType.Medium);
81+
QueueSafeEject();
82+
return;
83+
}
84+
85+
// Check if vinyl player is connected to the radio system
86+
if (!_stationRadio.TryGetLinkedPoweredServer(playerUid, out _)) // Starlight - Station Radio Check oved to StationRadioReceiverSystem
87+
{
88+
_popups.PopupPredicted(Loc.GetString("vinyl-popout-no-radio-connection"), playerUid, null, PopupType.Medium);
89+
QueueSafeEject();
90+
return;
91+
}
92+
93+
// Get the audio length
94+
var resolved = _audio.ResolveSound(vinylComp.Song);
95+
var audioLength = _audio.GetAudioLength(resolved);
96+
var endTime = _timing.CurTime + audioLength;
97+
98+
// Track this vinyl with its player
99+
_trackingVinyls[vinylUid] = new TrackingData(playerUid, endTime);
100+
}
101+
102+
private void OnVinylRemoved(EntityUid uid, VinylPlayerComponent player, ref VinylRemovedEvent args)
103+
{
104+
// Stop tracking if the vinyl is removed
105+
_trackingVinyls.Remove(args.Vinyl);
106+
}
107+
108+
public override void Update(float frameTime)
109+
{
110+
base.Update(frameTime);
111+
112+
var currentTime = _timing.CurTime;
113+
114+
foreach (var (vinylUid, data) in _trackingVinyls.ToList())
115+
{
116+
// Check if the vinyl still exists
117+
if (!Exists(vinylUid)
118+
|| !Exists(data.VinylPlayerUid))
119+
{
120+
_trackingVinyls.Remove(vinylUid);
121+
continue;
122+
}
123+
124+
// Check if vinyl player is still on a station
125+
if (_stationSystem.GetOwningStation(data.VinylPlayerUid) == null)
126+
{
127+
_trackingVinyls.Remove(vinylUid);
128+
_popups.PopupPredicted(Loc.GetString("vinyl-popout-no-station"), data.VinylPlayerUid, null, PopupType.Medium);
129+
EjectVinyl(data.VinylPlayerUid, vinylUid);
130+
continue;
131+
}
132+
133+
// Check if vinyl player is still powered
134+
if (!_power.IsPowered(data.VinylPlayerUid))
135+
{
136+
_trackingVinyls.Remove(vinylUid);
137+
_popups.PopupPredicted(Loc.GetString("vinyl-popout-no-power"), data.VinylPlayerUid, null, PopupType.Medium);
138+
EjectVinyl(data.VinylPlayerUid, vinylUid);
139+
continue;
140+
}
141+
142+
// Check if vinyl player is still connected to the radio system
143+
if (!_stationRadio.TryGetLinkedPoweredServer(data.VinylPlayerUid, out _)) // Starlight - Station Radio Check oved to StationRadioReceiverSystem
144+
{
145+
_trackingVinyls.Remove(vinylUid);
146+
_popups.PopupPredicted(Loc.GetString("vinyl-popout-no-radio-connection"), data.VinylPlayerUid, null, PopupType.Medium);
147+
EjectVinyl(data.VinylPlayerUid, vinylUid);
148+
continue;
149+
}
150+
151+
// Check if playback has finished
152+
if (currentTime >= data.EndTime)
153+
{
154+
HandleVinylFinished(vinylUid);
155+
_trackingVinyls.Remove(vinylUid);
156+
}
157+
}
158+
}
159+
160+
private void EjectVinyl(EntityUid playerUid, EntityUid vinylUid)
161+
{
162+
if (!Exists(vinylUid)
163+
|| !Exists(playerUid)
164+
|| !TryComp<ItemSlotsComponent>(playerUid, out var itemSlots))
165+
return;
166+
167+
// Find the slot containing the vinyl
168+
foreach (var (_, slot) in itemSlots.Slots)
169+
if (slot.Item == vinylUid)
170+
{
171+
_itemSlots.TryEject(playerUid, slot, null, out _);
172+
return;
173+
}
174+
}
175+
176+
private void HandleVinylFinished(EntityUid vinylUid)
177+
{
178+
if (!TryComp<VinylSummonRuleComponent>(vinylUid, out var summonComp))
179+
return;
180+
181+
// Resolve the game rule ID and get the threat prototype if available
182+
var ruleId = ResolveGameRule(summonComp.GameRule, out var threat);
183+
184+
if (ruleId != null)
185+
{
186+
_gameTicker.StartGameRule(ruleId, out _);
187+
188+
// If we have a threat prototype with an announcement, send it
189+
if (threat != null)
190+
_chat.DispatchGlobalAnnouncement(Loc.GetString(threat.Announcement), playSound: true, colorOverride: Color.Red);
191+
}
192+
193+
var vinylXform = Transform(vinylUid);
194+
var vinylCoords = vinylXform.Coordinates;
195+
196+
// Remove from container
197+
if (_containers.TryGetContainingContainer((vinylUid, vinylXform, null), out var container))
198+
_containers.Remove(vinylUid, container);
199+
200+
// Play sound effect
201+
_audio.PlayPvs(summonComp.BurnSound, vinylCoords, summonComp.BurnSoundParams); // Starlight - Dehardcode BurnSoundParams
202+
203+
// Spawn ash at the vinyl's location
204+
Spawn(summonComp.AshPrototype, vinylCoords); // Starlight - Dehardcode ash prototype
205+
206+
// Delete the vinyl
207+
QueueDel(vinylUid);
208+
}
209+
210+
private string? ResolveGameRule(string gameRuleIdentifier, out NinjaHackingThreatPrototype? threat)
211+
{
212+
threat = null;
213+
214+
// Check if it's a weighted random pool
215+
if (_prototypeManager.TryIndex<WeightedRandomPrototype>(gameRuleIdentifier, out var weightedPool))
216+
{
217+
// Pick a random threat ID from the weighted pool
218+
var threatId = weightedPool.Pick(_random);
219+
220+
// Look up the threat prototype to get the actual game rule ID
221+
if (_prototypeManager.TryIndex<NinjaHackingThreatPrototype>(threatId, out threat))
222+
return threat.Rule;
223+
224+
return null;
225+
}
226+
227+
// Assume it's a direct game rule entity ID
228+
return gameRuleIdentifier;
229+
}
230+
}

Content.Shared/Radio/Components/RadioSpeakerComponent.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
using Content.Shared.Chat;
33
using Robust.Shared.GameStates;
44
using Robust.Shared.Prototypes;
5+
using Content.Shared._Goobstation.StationRadio.Systems; // Starlight - Radio Host _Goob -> _Goobstation
56

67
namespace Content.Shared.Radio.Components;
78

89
/// <summary>
910
/// Listens for radio messages and relays them to local chat.
1011
/// </summary>
1112
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
12-
[Access(typeof(SharedRadioDeviceSystem))]
13+
[Access(typeof(SharedRadioDeviceSystem), typeof(StationRadioReceiverSystem))] // Goobstation - Add StationRadioReceiverSystem access.
1314
public sealed partial class RadioSpeakerComponent : Component
1415
{
1516
/// <summary>
@@ -24,4 +25,20 @@ public sealed partial class RadioSpeakerComponent : Component
2425

2526
[DataField, AutoNetworkedField]
2627
public bool Enabled;
28+
29+
// Starlight - Radio Host LouderSpeech -> ParseRadioPrefix.
30+
/// <summary>
31+
/// Whether or not a message is parsed through the radio when when sent in local chat.
32+
/// </summary>
33+
[DataField, AutoNetworkedField]
34+
public bool ParseRadioPrefix;
35+
// Starlight - End
36+
37+
// Goobstation - Radio Host
38+
/// <summary>
39+
/// Does the radio need to be on a power grid to work?
40+
/// </summary>
41+
[DataField]
42+
public bool PowerRequired;
43+
// Goobstation - End
2744
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using Robust.Shared.GameStates;
2+
3+
namespace Content.Shared._Goobstation.StationRadio.Components; // Starlight - _Goob -> _Goobstation
4+
5+
[RegisterComponent, NetworkedComponent]
6+
public sealed partial class RadioRigComponent : Component;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using Robust.Shared.Audio;
2+
using Robust.Shared.GameStates;
3+
4+
namespace Content.Shared._Goobstation.StationRadio.Components; // Starlight - _Goob -> _Goobstation
5+
6+
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
7+
public sealed partial class StationRadioReceiverComponent : Component
8+
{
9+
/// <summary>
10+
/// The sound entity being played
11+
/// </summary>
12+
[DataField, AutoNetworkedField]
13+
public EntityUid? SoundEntity;
14+
15+
/// <summary>
16+
/// Is the radio turned on
17+
/// </summary>
18+
[DataField, AutoNetworkedField]
19+
public bool Active = true;
20+
21+
/// <summary>
22+
/// Default audio params for the played audio.
23+
/// </summary>
24+
[DataField, AutoNetworkedField]
25+
public AudioParams DefaultParams = AudioParams.Default.WithVolume(3.5f).WithMaxDistance(8f); // 8 is just the edge of the screen usually
26+
27+
// Moffstation - Add Low Volume mode
28+
/// <summary>
29+
/// Is Radio is playing at full or low volume.
30+
/// </summary>
31+
[DataField, AutoNetworkedField]
32+
public bool LowVolume;
33+
// Moffstation - End
34+
35+
// Starlight - Dehardcode LowVolume mode's gain.
36+
/// <summary>
37+
/// How quiet the low volume setting on a Station Radio is. Default is 10%.
38+
/// </summary>
39+
[DataField, AutoNetworkedField]
40+
public float LowVolumeGain = 0.1f;
41+
// Starlight - End
42+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Robust.Shared.GameStates;
2+
using Robust.Shared.Audio; // Starlight - Add Station Radio Resume Play
3+
4+
namespace Content.Shared._Goobstation.StationRadio.Components; // Starlight - _Goob -> _Goobstation
5+
6+
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
7+
public sealed partial class StationRadioServerComponent : Component
8+
// Starlight - Add the ability for Station Radios to resume play.
9+
{
10+
/// <summary>
11+
/// The song currently being broadcasted.
12+
/// Null if nothing is playing.
13+
/// </summary>
14+
[DataField, AutoNetworkedField]
15+
public SoundPathSpecifier? CurrentSong;
16+
17+
/// <summary>
18+
/// For determining where the sound should resume.
19+
/// </summary>
20+
[DataField, AutoNetworkedField]
21+
public TimeSpan? PlaybackStartTime;
22+
}
23+
// Starlight - End

0 commit comments

Comments
 (0)