diff --git a/Content.Server/Lathe/Components/LatheAnnouncingComponent.cs b/Content.Server/Lathe/Components/LatheAnnouncingComponent.cs
new file mode 100644
index 0000000000..16c30d98eb
--- /dev/null
+++ b/Content.Server/Lathe/Components/LatheAnnouncingComponent.cs
@@ -0,0 +1,17 @@
+using Content.Shared.Radio;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Lathe.Components;
+
+///
+/// Causes this entity to announce onto the provided channels when it receives new recipes from its server
+///
+[RegisterComponent]
+public sealed partial class LatheAnnouncingComponent : Component
+{
+ ///
+ /// Radio channels to broadcast to when a new set of recipes is received
+ ///
+ [DataField(required: true)]
+ public List> Channels = new();
+}
diff --git a/Content.Server/Lathe/LatheSystem.cs b/Content.Server/Lathe/LatheSystem.cs
index 68a5228bdf..4851f6b63d 100644
--- a/Content.Server/Lathe/LatheSystem.cs
+++ b/Content.Server/Lathe/LatheSystem.cs
@@ -8,6 +8,7 @@ using Content.Server.Materials;
using Content.Server.Popups;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
+using Content.Server.Radio.EntitySystems;
using Content.Server.Stack;
using Content.Shared.Atmos;
using Content.Shared.Chemistry.Components;
@@ -20,6 +21,7 @@ using Content.Shared.Emag.Systems;
using Content.Shared.Examine;
using Content.Shared.Lathe;
using Content.Shared.Lathe.Prototypes;
+using Content.Shared.Localizations;
using Content.Shared.Materials;
using Content.Shared.Power;
using Content.Shared.ReagentSpeed;
@@ -53,6 +55,7 @@ namespace Content.Server.Lathe
[Dependency] private readonly SharedSolutionContainerSystem _solution = default!;
[Dependency] private readonly StackSystem _stack = default!;
[Dependency] private readonly TransformSystem _transform = default!;
+ [Dependency] private readonly RadioSystem _radio = default!;
///
/// Per-tick cache
@@ -67,6 +70,7 @@ namespace Content.Server.Lathe
SubscribeLocalEvent(OnMapInit);
SubscribeLocalEvent(OnPowerChanged);
SubscribeLocalEvent(OnDatabaseModified);
+ SubscribeLocalEvent(OnTechnologyDatabaseModified);
SubscribeLocalEvent(OnResearchRegistrationChanged);
SubscribeLocalEvent(OnLatheQueueRecipeMessage);
@@ -364,6 +368,41 @@ namespace Content.Server.Lathe
UpdateUserInterfaceState(uid, component);
}
+ private void OnTechnologyDatabaseModified(Entity ent, ref TechnologyDatabaseModifiedEvent args)
+ {
+ if (args.NewlyUnlockedRecipes is null)
+ return;
+
+ if (!TryGetAvailableRecipes(ent.Owner, out var potentialRecipes))
+ return;
+
+ var recipeNames = new List();
+ foreach (var recipeId in args.NewlyUnlockedRecipes)
+ {
+ if (!potentialRecipes.Contains(new(recipeId)))
+ continue;
+
+ if (!_proto.TryIndex(recipeId, out LatheRecipePrototype? recipe))
+ continue;
+
+ var itemName = GetRecipeName(recipe!);
+ recipeNames.Add(Loc.GetString("lathe-unlock-recipe-radio-broadcast-item", ("item", itemName)));
+ }
+
+ if (recipeNames.Count == 0)
+ return;
+
+ var message = Loc.GetString(
+ "lathe-unlock-recipe-radio-broadcast",
+ ("items", ContentLocalizationManager.FormatList(recipeNames))
+ );
+
+ foreach (var channel in ent.Comp.Channels)
+ {
+ _radio.SendRadioMessage(ent.Owner, message, channel, ent.Owner, escapeMarkup: false);
+ }
+ }
+
private void OnResearchRegistrationChanged(EntityUid uid, LatheComponent component, ref ResearchRegistrationChangedEvent args)
{
UpdateUserInterfaceState(uid, component);
diff --git a/Content.Server/Research/Systems/ResearchSystem.Technology.cs b/Content.Server/Research/Systems/ResearchSystem.Technology.cs
index 360985b4b6..0237c8712a 100644
--- a/Content.Server/Research/Systems/ResearchSystem.Technology.cs
+++ b/Content.Server/Research/Systems/ResearchSystem.Technology.cs
@@ -119,15 +119,17 @@ public sealed partial class ResearchSystem
}
component.UnlockedTechnologies.Add(technology.ID);
+ var addedRecipes = new List();
foreach (var unlock in technology.RecipeUnlocks)
{
if (component.UnlockedRecipes.Contains(unlock))
continue;
component.UnlockedRecipes.Add(unlock);
+ addedRecipes.Add(unlock);
}
Dirty(uid, component);
- var ev = new TechnologyDatabaseModifiedEvent();
+ var ev = new TechnologyDatabaseModifiedEvent(addedRecipes);
RaiseLocalEvent(uid, ref ev);
}
diff --git a/Content.Shared/Research/Components/TechnologyDatabaseComponent.cs b/Content.Shared/Research/Components/TechnologyDatabaseComponent.cs
index 4f976869f7..a69e4ee9dd 100644
--- a/Content.Shared/Research/Components/TechnologyDatabaseComponent.cs
+++ b/Content.Shared/Research/Components/TechnologyDatabaseComponent.cs
@@ -54,7 +54,7 @@ public sealed partial class TechnologyDatabaseComponent : Component
/// server to all of it's clients.
///
[ByRefEvent]
-public readonly record struct TechnologyDatabaseModifiedEvent;
+public readonly record struct TechnologyDatabaseModifiedEvent(List? NewlyUnlockedRecipes);
///
/// Event raised on a database after being synchronized
diff --git a/Content.Shared/Research/Systems/BlueprintSystem.cs b/Content.Shared/Research/Systems/BlueprintSystem.cs
index 237ff70300..903e529089 100644
--- a/Content.Shared/Research/Systems/BlueprintSystem.cs
+++ b/Content.Shared/Research/Systems/BlueprintSystem.cs
@@ -7,6 +7,7 @@ using Content.Shared.Research.Prototypes;
using Content.Shared.Whitelist;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
+using System.Linq;
namespace Content.Shared.Research.Systems;
@@ -64,7 +65,7 @@ public sealed class BlueprintSystem : EntitySystem
_container.Insert(blueprint.Owner, _container.GetContainer(ent, ent.Comp.ContainerId));
- var ev = new TechnologyDatabaseModifiedEvent();
+ var ev = new TechnologyDatabaseModifiedEvent(blueprint.Comp.ProvidedRecipes.Select(it => it.Id).ToList());
RaiseLocalEvent(ent, ref ev);
return true;
}
diff --git a/Content.Shared/Research/Systems/SharedResearchSystem.cs b/Content.Shared/Research/Systems/SharedResearchSystem.cs
index bca1ae4888..7ed33f7204 100644
--- a/Content.Shared/Research/Systems/SharedResearchSystem.cs
+++ b/Content.Shared/Research/Systems/SharedResearchSystem.cs
@@ -301,7 +301,7 @@ public abstract class SharedResearchSystem : EntitySystem
component.UnlockedRecipes.Add(recipe);
Dirty(uid, component);
- var ev = new TechnologyDatabaseModifiedEvent();
+ var ev = new TechnologyDatabaseModifiedEvent(new List { recipe });
RaiseLocalEvent(uid, ref ev);
}
}
diff --git a/Resources/Locale/en-US/lathe/lathesystem.ftl b/Resources/Locale/en-US/lathe/lathesystem.ftl
index 9fa62e0c1e..93e0107f68 100644
--- a/Resources/Locale/en-US/lathe/lathesystem.ftl
+++ b/Resources/Locale/en-US/lathe/lathesystem.ftl
@@ -1 +1,3 @@
lathe-popup-material-not-used = This material is not used in this machine.
+lathe-unlock-recipe-radio-broadcast = This lathe is now capable of producing the following recipes: {$items}
+lathe-unlock-recipe-radio-broadcast-item = [bold]{$item}[/bold]
diff --git a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
index cf3bcac302..0d008ed1e1 100644
--- a/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
+++ b/Resources/Prototypes/Entities/Structures/Machines/lathe.yml
@@ -408,6 +408,8 @@
- Sheet
- RawMaterial
- Ingot
+ - type: LatheAnnouncing
+ channels: [Security]
- type: entity
id: AmmoTechFab
@@ -478,6 +480,8 @@
board: MedicalTechFabCircuitboard
- type: StealTarget
stealGroup: MedicalTechFabCircuitboard
+ - type: LatheAnnouncing
+ channels: [Medical]
- type: entity
parent: BaseLathe