diff --git a/Content.Client/Administration/UI/AdminAnnounceWindow.xaml b/Content.Client/Administration/UI/AdminAnnounceWindow.xaml
new file mode 100644
index 0000000000..35c63cb975
--- /dev/null
+++ b/Content.Client/Administration/UI/AdminAnnounceWindow.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/AdminAnnounceWindow.xaml.cs b/Content.Client/Administration/UI/AdminAnnounceWindow.xaml.cs
new file mode 100644
index 0000000000..e8344cd127
--- /dev/null
+++ b/Content.Client/Administration/UI/AdminAnnounceWindow.xaml.cs
@@ -0,0 +1,47 @@
+using Content.Client.HUD;
+using Content.Shared.Administration;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.IoC;
+using Robust.Shared.Localization;
+
+namespace Content.Client.Administration.UI
+{
+ [GenerateTypedNameReferences]
+ public partial class AdminAnnounceWindow : SS14Window
+ {
+ [Dependency] private readonly IGameHud? _gameHud = default!;
+ [Dependency] private readonly ILocalizationManager _localization = default!;
+ public Button AnnounceButton => _announceButton;
+ public OptionButton AnnounceMethod => _announceMethod;
+ public LineEdit Announcer => _announcer;
+ public LineEdit Announcement => _announcement;
+
+ public AdminAnnounceWindow()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ AnnounceMethod.AddItem(_localization.GetString("announce-type-station"));
+ AnnounceMethod.SetItemMetadata(0, AdminAnnounceType.Station);
+ AnnounceMethod.AddItem(_localization.GetString("announce-type-server"));
+ AnnounceMethod.SetItemMetadata(1, AdminAnnounceType.Server);
+ AnnounceMethod.OnItemSelected += AnnounceMethodOnOnItemSelected;
+ Announcement.OnTextChanged += AnnouncementOnOnTextChanged;
+ }
+
+
+ private void AnnouncementOnOnTextChanged(LineEdit.LineEditEventArgs args)
+ {
+ AnnounceButton.Disabled = args.Text.TrimStart() == "";
+ }
+
+ private void AnnounceMethodOnOnItemSelected(OptionButton.ItemSelectedEventArgs args)
+ {
+ AnnounceMethod.SelectId(args.Id);
+ Announcer.Editable = ((AdminAnnounceType?)args.Button.SelectedMetadata ?? AdminAnnounceType.Station) == AdminAnnounceType.Station;
+ }
+ }
+}
diff --git a/Content.Client/Administration/UI/AdminMenuWindowEui.cs b/Content.Client/Administration/UI/AdminMenuWindowEui.cs
new file mode 100644
index 0000000000..f26ae5a1a6
--- /dev/null
+++ b/Content.Client/Administration/UI/AdminMenuWindowEui.cs
@@ -0,0 +1,40 @@
+using Content.Client.Eui;
+using Content.Shared.Administration;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client.Administration.UI
+{
+ public class AdminAnnounceEui : BaseEui
+ {
+ private readonly AdminAnnounceWindow _window;
+
+ public AdminAnnounceEui()
+ {
+ _window = new AdminAnnounceWindow();
+ _window.OnClose += () => SendMessage(new AdminAnnounceEuiMsg.Close());
+ _window.AnnounceButton.OnPressed += AnnounceButtonOnOnPressed;
+ }
+
+ private void AnnounceButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
+ {
+ SendMessage(new AdminAnnounceEuiMsg.DoAnnounce
+ {
+ Announcement = _window.Announcement.Text,
+ Announcer = _window.Announcer.Text,
+ AnnounceType = (AdminAnnounceType) (_window.AnnounceMethod.SelectedMetadata ?? AdminAnnounceType.Station),
+ CloseAfter = true,
+ });
+
+ }
+
+ public override void Opened()
+ {
+ _window.OpenCentered();
+ }
+
+ public override void Closed()
+ {
+ _window.Close();
+ }
+ }
+}
diff --git a/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml b/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
index 4c8e37d7ee..da899fe580 100644
--- a/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
+++ b/Content.Client/Administration/UI/Tabs/AdminTab/AdminTab.xaml
@@ -12,6 +12,7 @@
+
diff --git a/Content.Server/Administration/Commands/AnnounceUiCommand.cs b/Content.Server/Administration/Commands/AnnounceUiCommand.cs
new file mode 100644
index 0000000000..0d171a9b4b
--- /dev/null
+++ b/Content.Server/Administration/Commands/AnnounceUiCommand.cs
@@ -0,0 +1,33 @@
+using Content.Server.Administration.UI;
+using Content.Server.EUI;
+using Content.Shared.Administration;
+using Robust.Server.Player;
+using Robust.Shared.Console;
+using Robust.Shared.IoC;
+
+namespace Content.Server.Administration.Commands
+{
+ [AdminCommand(AdminFlags.Fun)]
+ public class AnnounceUiCommand : IConsoleCommand
+ {
+ public string Command => "announceui";
+
+ public string Description => "Opens the announcement UI";
+
+ public string Help => $"{Command}";
+
+ public void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ var player = shell.Player as IPlayerSession;
+ if (player == null)
+ {
+ shell.WriteLine("This does not work from the server console.");
+ return;
+ }
+
+ var eui = IoCManager.Resolve();
+ var ui = new AdminAnnounceEui();
+ eui.OpenEui(ui, player);
+ }
+ }
+}
diff --git a/Content.Server/Administration/UI/AdminAnnounceEui.cs b/Content.Server/Administration/UI/AdminAnnounceEui.cs
new file mode 100644
index 0000000000..6d9f2b9727
--- /dev/null
+++ b/Content.Server/Administration/UI/AdminAnnounceEui.cs
@@ -0,0 +1,63 @@
+using Content.Server.Administration.Managers;
+using Content.Server.Chat.Managers;
+using Content.Server.EUI;
+using Content.Shared.Administration;
+using Content.Shared.Eui;
+using Robust.Shared.IoC;
+
+namespace Content.Server.Administration.UI
+{
+ public sealed class AdminAnnounceEui : BaseEui
+ {
+ [Dependency] private readonly IAdminManager _adminManager = default!;
+ [Dependency] private readonly IChatManager _chatManager = default!;
+
+ public AdminAnnounceEui()
+ {
+ IoCManager.InjectDependencies(this);
+ }
+
+ public override void Opened()
+ {
+ StateDirty();
+ }
+
+ public override EuiStateBase GetNewState()
+ {
+ return new AdminAnnounceEuiState();
+ }
+
+ public override void HandleMessage(EuiMessageBase msg)
+ {
+ switch (msg)
+ {
+ case AdminAnnounceEuiMsg.Close:
+ Close();
+ break;
+ case AdminAnnounceEuiMsg.DoAnnounce doAnnounce:
+ if (!_adminManager.HasAdminFlag(Player, AdminFlags.Fun))
+ {
+ Close();
+ break;
+ }
+
+ switch (doAnnounce.AnnounceType)
+ {
+ case AdminAnnounceType.Server:
+ _chatManager.DispatchServerAnnouncement(doAnnounce.Announcement);
+ break;
+ case AdminAnnounceType.Station:
+ _chatManager.DispatchStationAnnouncement(doAnnounce.Announcement, doAnnounce.Announcer);
+ break;
+ }
+
+ StateDirty();
+
+ if (doAnnounce.CloseAfter)
+ Close();
+
+ break;
+ }
+ }
+ }
+}
diff --git a/Content.Shared/Administration/AdminAnnounceEuiState.cs b/Content.Shared/Administration/AdminAnnounceEuiState.cs
new file mode 100644
index 0000000000..90b608f13c
--- /dev/null
+++ b/Content.Shared/Administration/AdminAnnounceEuiState.cs
@@ -0,0 +1,32 @@
+using System;
+using Content.Shared.Eui;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Administration
+{
+ public enum AdminAnnounceType
+ {
+ Station,
+ Server,
+ }
+ [Serializable, NetSerializable]
+ public sealed class AdminAnnounceEuiState : EuiStateBase {}
+
+ public static class AdminAnnounceEuiMsg
+ {
+ [Serializable, NetSerializable]
+ public sealed class Close : EuiMessageBase
+ {
+
+ }
+
+ [Serializable, NetSerializable]
+ public sealed class DoAnnounce : EuiMessageBase
+ {
+ public bool CloseAfter;
+ public string Announcer = default!;
+ public string Announcement = default!;
+ public AdminAnnounceType AnnounceType;
+ }
+ }
+}
diff --git a/Resources/Locale/en-US/administration/ui/admin-announce-window.ftl b/Resources/Locale/en-US/administration/ui/admin-announce-window.ftl
new file mode 100644
index 0000000000..3f386a966a
--- /dev/null
+++ b/Resources/Locale/en-US/administration/ui/admin-announce-window.ftl
@@ -0,0 +1,5 @@
+admin-announce-title = Make Announcement
+announcer-placeholder = Announcer
+announcement-placeholder = Announcement text
+announce-type-station = Station
+announce-type-server = Server