diff --git a/Content.Client/Administration/UI/AdminMenuWindow.xaml b/Content.Client/Administration/UI/AdminMenuWindow.xaml
index 311d67b826c..d3d3df02d93 100644
--- a/Content.Client/Administration/UI/AdminMenuWindow.xaml
+++ b/Content.Client/Administration/UI/AdminMenuWindow.xaml
@@ -6,7 +6,8 @@
xmlns:tabs="clr-namespace:Content.Client.Administration.UI.Tabs"
xmlns:playerTab="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab"
xmlns:objectsTab="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
- xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab">
+ xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab"
+ xmlns:baby="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab">
@@ -14,6 +15,7 @@
+
diff --git a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
index c3ea67a3edb..f3aa2572f2f 100644
--- a/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
+++ b/Content.Client/Administration/UI/AdminMenuWindow.xaml.cs
@@ -21,8 +21,12 @@ public AdminMenuWindow()
MasterTabContainer.SetTabTitle(3, Loc.GetString("admin-menu-round-tab"));
MasterTabContainer.SetTabTitle(4, Loc.GetString("admin-menu-server-tab"));
MasterTabContainer.SetTabTitle(5, Loc.GetString("admin-menu-panic-bunker-tab"));
- MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-players-tab"));
- MasterTabContainer.SetTabTitle(7, Loc.GetString("admin-menu-objects-tab"));
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+ MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-baby-jail-tab"));
+ MasterTabContainer.SetTabTitle(7, Loc.GetString("admin-menu-players-tab"));
+ MasterTabContainer.SetTabTitle(8, Loc.GetString("admin-menu-objects-tab"));
}
protected override void Dispose(bool disposing)
diff --git a/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml
new file mode 100644
index 00000000000..3432f11669f
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml.cs b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml.cs
new file mode 100644
index 00000000000..4e5f679b458
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailStatusWindow.xaml.cs
@@ -0,0 +1,18 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
+
+/*
+ * TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+[GenerateTypedNameReferences]
+public sealed partial class BabyJailStatusWindow : DefaultWindow
+{
+ public BabyJailStatusWindow()
+ {
+ RobustXamlLoader.Load(this);
+ }
+}
diff --git a/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml
new file mode 100644
index 00000000000..dd770c2be53
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml.cs b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml.cs
new file mode 100644
index 00000000000..aa9d6ced951
--- /dev/null
+++ b/Content.Client/Administration/UI/Tabs/BabyJailTab/BabyJailTab.xaml.cs
@@ -0,0 +1,75 @@
+using Content.Shared.Administration.Events;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Console;
+
+/*
+ * TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
+
+[GenerateTypedNameReferences]
+public sealed partial class BabyJailTab : Control
+{
+ [Dependency] private readonly IConsoleHost _console = default!;
+
+ private string _maxAccountAge;
+ private string _maxOverallMinutes;
+
+ public BabyJailTab()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ MaxAccountAge.OnTextEntered += args => SendMaxAccountAge(args.Text);
+ MaxAccountAge.OnFocusExit += args => SendMaxAccountAge(args.Text);
+ _maxAccountAge = MaxAccountAge.Text;
+
+ MaxOverallMinutes.OnTextEntered += args => SendMaxOverallMinutes(args.Text);
+ MaxOverallMinutes.OnFocusExit += args => SendMaxOverallMinutes(args.Text);
+ _maxOverallMinutes = MaxOverallMinutes.Text;
+ }
+
+ private void SendMaxAccountAge(string text)
+ {
+ if (string.IsNullOrWhiteSpace(text) ||
+ text == _maxAccountAge ||
+ !int.TryParse(text, out var minutes))
+ {
+ return;
+ }
+
+ _console.ExecuteCommand($"babyjail_max_account_age {minutes}");
+ }
+
+ private void SendMaxOverallMinutes(string text)
+ {
+ if (string.IsNullOrWhiteSpace(text) ||
+ text == _maxOverallMinutes ||
+ !int.TryParse(text, out var minutes))
+ {
+ return;
+ }
+
+ _console.ExecuteCommand($"babyjail_max_overall_minutes {minutes}");
+ }
+
+ public void UpdateStatus(BabyJailStatus status)
+ {
+ EnabledButton.Pressed = status.Enabled;
+ EnabledButton.Text = Loc.GetString(status.Enabled
+ ? "admin-ui-baby-jail-enabled"
+ : "admin-ui-baby-jail-disabled"
+ );
+ EnabledButton.ModulateSelfOverride = status.Enabled ? Color.Red : null;
+ ShowReasonButton.Pressed = status.ShowReason;
+
+ MaxAccountAge.Text = status.MaxAccountAgeMinutes.ToString();
+ _maxAccountAge = MaxAccountAge.Text;
+
+ MaxOverallMinutes.Text = status.MaxOverallMinutes.ToString();
+ _maxOverallMinutes = MaxOverallMinutes.Text;
+ }
+}
diff --git a/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs b/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs
index 3d8235591ad..e12ce0ab011 100644
--- a/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs
+++ b/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs
@@ -3,6 +3,7 @@
using Content.Client.Administration.UI;
using Content.Client.Administration.UI.Tabs.ObjectsTab;
using Content.Client.Administration.UI.Tabs.PanicBunkerTab;
+using Content.Client.Administration.UI.Tabs.BabyJailTab;
using Content.Client.Administration.UI.Tabs.PlayerTab;
using Content.Client.Gameplay;
using Content.Client.Lobby;
@@ -37,11 +38,13 @@ public sealed class AdminUIController : UIController,
private AdminMenuWindow? _window;
private MenuButton? AdminButton => UIManager.GetActiveUIWidgetOrNull()?.AdminButton;
private PanicBunkerStatus? _panicBunker;
+ private BabyJailStatus? _babyJail;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent(OnPanicBunkerUpdated);
+ SubscribeNetworkEvent(OnBabyJailUpdated);
}
private void OnPanicBunkerUpdated(PanicBunkerChangedEvent msg, EntitySessionEventArgs args)
@@ -56,6 +59,18 @@ private void OnPanicBunkerUpdated(PanicBunkerChangedEvent msg, EntitySessionEven
}
}
+ private void OnBabyJailUpdated(BabyJailChangedEvent msg, EntitySessionEventArgs args)
+ {
+ var showDialog = _babyJail == null && msg.Status.Enabled;
+ _babyJail = msg.Status;
+ _window?.BabyJailControl.UpdateStatus(msg.Status);
+
+ if (showDialog)
+ {
+ UIManager.CreateWindow().OpenCentered();
+ }
+ }
+
public void OnStateEntered(GameplayState state)
{
EnsureWindow();
@@ -101,6 +116,13 @@ private void EnsureWindow()
if (_panicBunker != null)
_window.PanicBunkerControl.UpdateStatus(_panicBunker);
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+ if (_babyJail != null)
+ _window.BabyJailControl.UpdateStatus(_babyJail);
+
_window.PlayerTabControl.OnEntryKeyBindDown += PlayerTabEntryKeyBindDown;
_window.ObjectsTabControl.OnEntryKeyBindDown += ObjectsTabEntryKeyBindDown;
_window.OnOpen += OnWindowOpen;
diff --git a/Content.Server.Database/Model.cs b/Content.Server.Database/Model.cs
index 60935e8f06a..3510a5422e3 100644
--- a/Content.Server.Database/Model.cs
+++ b/Content.Server.Database/Model.cs
@@ -889,6 +889,10 @@ public enum ConnectionDenyReason : byte
Whitelist = 1,
Full = 2,
Panic = 3,
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+ BabyJail = 4,
}
public class ServerBanHit
diff --git a/Content.Server/Administration/Commands/BabyJailCommand.cs b/Content.Server/Administration/Commands/BabyJailCommand.cs
new file mode 100644
index 00000000000..058b67ca528
--- /dev/null
+++ b/Content.Server/Administration/Commands/BabyJailCommand.cs
@@ -0,0 +1,139 @@
+using Content.Shared.Administration;
+using Content.Shared.CCVar;
+using Robust.Shared.Configuration;
+using Robust.Shared.Console;
+
+/*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+namespace Content.Server.Administration.Commands;
+
+[AdminCommand(AdminFlags.Server)]
+public sealed class BabyJailCommand : LocalizedCommands
+{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ public override string Command => "babyjail";
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ var toggle = Toggle(CCVars.BabyJailEnabled, shell, args, _cfg);
+ if (toggle == null)
+ return;
+
+ shell.WriteLine(Loc.GetString(toggle.Value ? "babyjail-command-enabled" : "babyjail-command-disabled"));
+ }
+
+ public static bool? Toggle(CVarDef cvar, IConsoleShell shell, string[] args, IConfigurationManager config)
+ {
+ if (args.Length > 1)
+ {
+ shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
+ return null;
+ }
+
+ var enabled = config.GetCVar(cvar);
+
+ switch (args.Length)
+ {
+ case 0:
+ enabled = !enabled;
+ break;
+ case 1 when !bool.TryParse(args[0], out enabled):
+ shell.WriteError(Loc.GetString("shell-argument-must-be-boolean"));
+ return null;
+ }
+
+ config.SetCVar(cvar, enabled);
+
+ return enabled;
+ }
+}
+
+
+[AdminCommand(AdminFlags.Server)]
+public sealed class BabyJailShowReasonCommand : LocalizedCommands
+{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ public override string Command => "babyjail_show_reason";
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ var toggle = BabyJailCommand.Toggle(CCVars.BabyJailShowReason, shell, args, _cfg);
+ if (toggle == null)
+ return;
+
+ shell.WriteLine(Loc.GetString(toggle.Value
+ ? "babyjail-command-show-reason-enabled"
+ : "babyjail-command-show-reason-disabled"
+ ));
+ }
+}
+
+[AdminCommand(AdminFlags.Server)]
+public sealed class BabyJailMinAccountAgeCommand : LocalizedCommands
+{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ public override string Command => "babyjail_max_account_age";
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ switch (args.Length)
+ {
+ case 0:
+ {
+ var current = _cfg.GetCVar(CCVars.BabyJailMaxAccountAge);
+ shell.WriteLine(Loc.GetString("babyjail-command-max-account-age-is", ("minutes", current)));
+ break;
+ }
+ case > 1:
+ shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
+ return;
+ }
+
+ if (!int.TryParse(args[0], out var minutes))
+ {
+ shell.WriteError(Loc.GetString("shell-argument-must-be-number"));
+ return;
+ }
+
+ _cfg.SetCVar(CCVars.BabyJailMaxAccountAge, minutes);
+ shell.WriteLine(Loc.GetString("babyjail-command-max-account-age-set", ("minutes", minutes)));
+ }
+}
+
+[AdminCommand(AdminFlags.Server)]
+public sealed class BabyJailMinOverallHoursCommand : LocalizedCommands
+{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ public override string Command => "babyjail_max_overall_minutes";
+
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ switch (args.Length)
+ {
+ case 0:
+ {
+ var current = _cfg.GetCVar(CCVars.BabyJailMaxOverallMinutes);
+ shell.WriteLine(Loc.GetString("babyjail-command-max-overall-minutes-is", ("minutes", current)));
+ break;
+ }
+ case > 1:
+ shell.WriteError(Loc.GetString("shell-need-between-arguments",("lower", 0), ("upper", 1)));
+ return;
+ }
+
+ if (!int.TryParse(args[0], out var hours))
+ {
+ shell.WriteError(Loc.GetString("shell-argument-must-be-number"));
+ return;
+ }
+
+ _cfg.SetCVar(CCVars.BabyJailMaxOverallMinutes, hours);
+ shell.WriteLine(Loc.GetString("babyjail-command-overall-minutes-set", ("hours", hours)));
+ }
+}
diff --git a/Content.Server/Administration/Systems/AdminSystem.cs b/Content.Server/Administration/Systems/AdminSystem.cs
index a1a6b33ebcb..a12d106880c 100644
--- a/Content.Server/Administration/Systems/AdminSystem.cs
+++ b/Content.Server/Administration/Systems/AdminSystem.cs
@@ -62,6 +62,7 @@ public sealed class AdminSystem : EntitySystem
private readonly HashSet _roundActivePlayers = new();
public readonly PanicBunkerStatus PanicBunker = new();
+ public readonly BabyJailStatus BabyJail = new();
public override void Initialize()
{
@@ -70,14 +71,25 @@ public override void Initialize()
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
_adminManager.OnPermsChanged += OnAdminPermsChanged;
+ // Panic Bunker Settings
Subs.CVar(_config, CCVars.PanicBunkerEnabled, OnPanicBunkerChanged, true);
Subs.CVar(_config, CCVars.PanicBunkerDisableWithAdmins, OnPanicBunkerDisableWithAdminsChanged, true);
Subs.CVar(_config, CCVars.PanicBunkerEnableWithoutAdmins, OnPanicBunkerEnableWithoutAdminsChanged, true);
Subs.CVar(_config, CCVars.PanicBunkerCountDeadminnedAdmins, OnPanicBunkerCountDeadminnedAdminsChanged, true);
- Subs.CVar(_config, CCVars.PanicBunkerShowReason, OnShowReasonChanged, true);
+ Subs.CVar(_config, CCVars.PanicBunkerShowReason, OnPanicBunkerShowReasonChanged, true);
Subs.CVar(_config, CCVars.PanicBunkerMinAccountAge, OnPanicBunkerMinAccountAgeChanged, true);
Subs.CVar(_config, CCVars.PanicBunkerMinOverallHours, OnPanicBunkerMinOverallHoursChanged, true);
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+ // Baby Jail Settings
+ Subs.CVar(_config, CCVars.BabyJailEnabled, OnBabyJailChanged, true);
+ Subs.CVar(_config, CCVars.BabyJailShowReason, OnBabyJailShowReasonChanged, true);
+ Subs.CVar(_config, CCVars.BabyJailMaxAccountAge, OnBabyJailMaxAccountAgeChanged, true);
+ Subs.CVar(_config, CCVars.BabyJailMaxOverallMinutes, OnBabyJailMaxOverallMinutesChanged, true);
+
SubscribeLocalEvent(OnIdentityChanged);
SubscribeLocalEvent(OnPlayerAttached);
SubscribeLocalEvent(OnPlayerDetached);
@@ -249,6 +261,17 @@ private void OnPanicBunkerChanged(bool enabled)
SendPanicBunkerStatusAll();
}
+ private void OnBabyJailChanged(bool enabled)
+ {
+ BabyJail.Enabled = enabled;
+ _chat.SendAdminAlert(Loc.GetString(enabled
+ ? "admin-ui-baby-jail-enabled-admin-alert"
+ : "admin-ui-baby-jail-disabled-admin-alert"
+ ));
+
+ SendBabyJailStatusAll();
+ }
+
private void OnPanicBunkerDisableWithAdminsChanged(bool enabled)
{
PanicBunker.DisableWithAdmins = enabled;
@@ -267,24 +290,42 @@ private void OnPanicBunkerCountDeadminnedAdminsChanged(bool enabled)
UpdatePanicBunker();
}
- private void OnShowReasonChanged(bool enabled)
+ private void OnPanicBunkerShowReasonChanged(bool enabled)
{
PanicBunker.ShowReason = enabled;
SendPanicBunkerStatusAll();
}
+ private void OnBabyJailShowReasonChanged(bool enabled)
+ {
+ BabyJail.ShowReason = enabled;
+ SendBabyJailStatusAll();
+ }
+
private void OnPanicBunkerMinAccountAgeChanged(int minutes)
{
PanicBunker.MinAccountAgeHours = minutes / 60;
SendPanicBunkerStatusAll();
}
+ private void OnBabyJailMaxAccountAgeChanged(int minutes)
+ {
+ BabyJail.MaxAccountAgeMinutes = minutes;
+ SendBabyJailStatusAll();
+ }
+
private void OnPanicBunkerMinOverallHoursChanged(int hours)
{
PanicBunker.MinOverallHours = hours;
SendPanicBunkerStatusAll();
}
+ private void OnBabyJailMaxOverallMinutesChanged(int minutes)
+ {
+ BabyJail.MaxOverallMinutes = minutes;
+ SendBabyJailStatusAll();
+ }
+
private void UpdatePanicBunker()
{
var admins = PanicBunker.CountDeadminnedAdmins
@@ -326,6 +367,15 @@ private void SendPanicBunkerStatusAll()
}
}
+ private void SendBabyJailStatusAll()
+ {
+ var ev = new BabyJailChangedEvent(BabyJail);
+ foreach (var admin in _adminManager.AllAdmins)
+ {
+ RaiseNetworkEvent(ev, admin);
+ }
+ }
+
///
/// Erases a player from the round.
/// This removes them and any trace of them from the round, deleting their
diff --git a/Content.Server/Connection/ConnectionManager.cs b/Content.Server/Connection/ConnectionManager.cs
index cd89f48d49f..2211a245322 100644
--- a/Content.Server/Connection/ConnectionManager.cs
+++ b/Content.Server/Connection/ConnectionManager.cs
@@ -13,6 +13,9 @@
using Robust.Shared.Network;
using Robust.Shared.Timing;
+/*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
namespace Content.Server.Connection
{
@@ -125,6 +128,10 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
}
}
+ /*
+ * TODO: Jesus H Christ what is this utter mess of a function
+ * TODO: Break this apart into is constituent steps.
+ */
private async Task<(ConnectionDenyReason, string, List? bansHit)?> ShouldDeny(
NetConnectingArgs e)
{
@@ -202,6 +209,14 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
}
}
+ if (_cfg.GetCVar(CCVars.BabyJailEnabled) && adminData == null)
+ {
+ var result = await IsInvalidConnectionDueToBabyJail(userId, e);
+
+ if (result.IsInvalid)
+ return (ConnectionDenyReason.BabyJail, result.Reason, null);
+ }
+
var wasInGame = EntitySystem.TryGet(out var ticker) &&
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
status == PlayerGameStatus.JoinedGame;
@@ -231,6 +246,57 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
return null;
}
+ private async Task<(bool IsInvalid, string Reason)> IsInvalidConnectionDueToBabyJail(NetUserId userId, NetConnectingArgs e)
+ {
+ // If you're whitelisted then bypass this whole thing
+ if (await _db.GetWhitelistStatusAsync(userId))
+ return (false, "");
+
+ // Initial cvar retrieval
+ var showReason = _cfg.GetCVar(CCVars.BabyJailShowReason);
+ var reason = _cfg.GetCVar(CCVars.BabyJailCustomReason);
+ var maxAccountAgeMinutes = _cfg.GetCVar(CCVars.BabyJailMaxAccountAge);
+ var maxPlaytimeMinutes = _cfg.GetCVar(CCVars.BabyJailMaxOverallMinutes);
+
+ // Wait some time to lookup data
+ var record = await _dbManager.GetPlayerRecordByUserId(userId);
+
+ var isAccountAgeInvalid = record == null || record.FirstSeenTime.CompareTo(DateTimeOffset.Now - TimeSpan.FromMinutes(maxAccountAgeMinutes)) <= 0;
+ if (isAccountAgeInvalid && showReason)
+ {
+ var locAccountReason = reason != string.Empty
+ ? reason
+ : Loc.GetString("baby-jail-account-denied-reason",
+ ("reason",
+ Loc.GetString(
+ "baby-jail-account-reason-account",
+ ("minutes", maxAccountAgeMinutes))));
+
+ return (true, locAccountReason);
+ }
+
+ var overallTime = ( await _db.GetPlayTimes(e.UserId)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall);
+ var isTotalPlaytimeInvalid = overallTime == null || overallTime.TimeSpent.TotalMinutes >= maxPlaytimeMinutes;
+
+ if (isTotalPlaytimeInvalid && showReason)
+ {
+ var locPlaytimeReason = reason != string.Empty
+ ? reason
+ : Loc.GetString("baby-jail-account-denied-reason",
+ ("reason",
+ Loc.GetString(
+ "baby-jail-account-reason-overall",
+ ("minutes", maxPlaytimeMinutes))));
+
+ return (true, locPlaytimeReason);
+ }
+
+ if (!showReason && isTotalPlaytimeInvalid || isAccountAgeInvalid)
+ return (true, Loc.GetString("baby-jail-account-denied"));
+
+ return (false, "");
+ }
+
private bool HasTemporaryBypass(NetUserId user)
{
return _temporaryBypasses.TryGetValue(user, out var time) && time > _gameTiming.RealTime;
diff --git a/Content.Server/GameTicking/GameTicker.StatusShell.cs b/Content.Server/GameTicking/GameTicker.StatusShell.cs
index fcf5b1c25cd..67367b94b7f 100644
--- a/Content.Server/GameTicking/GameTicker.StatusShell.cs
+++ b/Content.Server/GameTicking/GameTicker.StatusShell.cs
@@ -46,6 +46,12 @@ private void GetStatusResponse(JsonNode jObject)
jObject["players"] = _playerManager.PlayerCount;
jObject["soft_max_players"] = _cfg.GetCVar(CCVars.SoftMaxPlayers);
jObject["panic_bunker"] = _cfg.GetCVar(CCVars.PanicBunkerEnabled);
+
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+ jObject["baby_jail"] = _cfg.GetCVar(CCVars.BabyJailEnabled);
jObject["run_level"] = (int) _runLevel;
if (preset != null)
jObject["preset"] = Loc.GetString(preset.ModeTitle);
diff --git a/Content.Shared/Administration/Events/BabyJailChangedEvent.cs b/Content.Shared/Administration/Events/BabyJailChangedEvent.cs
new file mode 100644
index 00000000000..56d5ce51626
--- /dev/null
+++ b/Content.Shared/Administration/Events/BabyJailChangedEvent.cs
@@ -0,0 +1,22 @@
+using Robust.Shared.Serialization;
+
+/*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+namespace Content.Shared.Administration.Events;
+
+[Serializable, NetSerializable]
+public sealed class BabyJailStatus
+{
+ public bool Enabled;
+ public bool ShowReason;
+ public int MaxAccountAgeMinutes;
+ public int MaxOverallMinutes;
+}
+
+[Serializable, NetSerializable]
+public sealed class BabyJailChangedEvent(BabyJailStatus status) : EntityEventArgs
+{
+ public BabyJailStatus Status = status;
+}
diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs
index a872a3d4e7b..491e5343959 100644
--- a/Content.Shared/CCVar/CCVars.cs
+++ b/Content.Shared/CCVar/CCVars.cs
@@ -320,6 +320,48 @@ public static readonly CVarDef
public static readonly CVarDef BypassBunkerWhitelist =
CVarDef.Create("game.panic_bunker.whitelisted_can_bypass", true, CVar.SERVERONLY);
+ /*
+ * TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
+ */
+
+ ///
+ /// Whether the baby jail is currently enabled.
+ ///
+ public static readonly CVarDef BabyJailEnabled =
+ CVarDef.Create("game.baby_jail.enabled", false, CVar.NOTIFY | CVar.REPLICATED | CVar.SERVER);
+
+ ///
+ /// Show reason of disconnect for user or not.
+ ///
+ public static readonly CVarDef BabyJailShowReason =
+ CVarDef.Create("game.baby_jail.show_reason", false, CVar.SERVERONLY);
+
+ ///
+ /// Maximum age of the account (from server's PoV, so from first-seen date) in minutes that can access baby
+ /// jailed servers.
+ ///
+ public static readonly CVarDef BabyJailMaxAccountAge =
+ CVarDef.Create("game.baby_jail.max_account_age", 1440, CVar.SERVERONLY);
+
+ ///
+ /// Maximum overall played time allowed to access baby jailed servers.
+ ///
+ public static readonly CVarDef BabyJailMaxOverallMinutes =
+ CVarDef.Create("game.baby_jail.max_overall_minutes", 120, CVar.SERVERONLY);
+
+ ///
+ /// A custom message that will be used for connections denied due to the baby jail.
+ /// If not empty, then will overwrite
+ ///
+ public static readonly CVarDef BabyJailCustomReason =
+ CVarDef.Create("game.baby_jail.custom_reason", string.Empty, CVar.SERVERONLY);
+
+ ///
+ /// Allow bypassing the baby jail if the user is whitelisted.
+ ///
+ public static readonly CVarDef BypassBabyJailWhitelist =
+ CVarDef.Create("game.baby_jail.whitelisted_can_bypass", true, CVar.SERVERONLY);
+
///
/// Make people bonk when trying to climb certain objects like tables.
///
diff --git a/Resources/Locale/en-US/administration/commands/babyjail.ftl b/Resources/Locale/en-US/administration/commands/babyjail.ftl
new file mode 100644
index 00000000000..5a9d9490517
--- /dev/null
+++ b/Resources/Locale/en-US/administration/commands/babyjail.ftl
@@ -0,0 +1,19 @@
+cmd-babyjail-desc = Toggles the baby jail, which enables stricter restrictions on who's allowed to join the server.
+cmd-babyjail-help = Usage: babyjail
+babyjail-command-enabled = Baby jail has been enabled.
+babyjail-command-disabled = Baby jail has been disabled.
+
+cmd-babyjail_show_reason-desc = Toggles whether or not to show connecting clients the reason why the baby jail blocked them from joining.
+cmd-babyjail_show_reason-help = Usage: babyjail_show_reason
+babyjail-command-show-reason-enabled = The baby jail will now show a reason to users it blocks from connecting.
+babyjail-command-show-reason-disabled = The baby jail will no longer show a reason to users it blocks from connecting.
+
+cmd-babyjail_max_account_age-desc = Gets or sets the maximum account age in minutes that an account can have to be allowed to connect with the baby jail enabled.
+cmd-babyjail_max_account_age-help = Usage: babyjail_max_account_age
+babyjail-command-max-account-age-is = The maximum account age for the baby jail is {$minutes} minutes.
+babyjail-command-max-account-age-set = Set the maximum account age for the baby jail to {$minutes} minutes.
+
+cmd-babyjail_max_overall_minutes-desc = Gets or sets the maximum overall playtime in minutes that an account can have to be allowed to connect with the baby jail enabled.
+cmd-babyjail_max_overall_minutes-help = Usage: babyjail_max_overall_minutes
+babyjail-command-max-overall-minutes-is = The maximum overall playtime for the baby jail is {$minutes} minutes.
+babyjail-command-max-overall-minutes-set = Set the maximum overall playtime for the baby jail to {$minutes} minutes.
diff --git a/Resources/Locale/en-US/administration/ui/admin-menu-window.ftl b/Resources/Locale/en-US/administration/ui/admin-menu-window.ftl
index c759e4c2cb1..03b2046a9e1 100644
--- a/Resources/Locale/en-US/administration/ui/admin-menu-window.ftl
+++ b/Resources/Locale/en-US/administration/ui/admin-menu-window.ftl
@@ -7,5 +7,6 @@ admin-menu-atmos-tab = Atmos
admin-menu-round-tab = Round
admin-menu-server-tab = Server
admin-menu-panic-bunker-tab = Panic Bunker
+admin-menu-baby-jail-tab = Baby Jail
admin-menu-players-tab = Players
admin-menu-objects-tab = Objects
diff --git a/Resources/Locale/en-US/administration/ui/tabs/babyjail-tab.ftl b/Resources/Locale/en-US/administration/ui/tabs/babyjail-tab.ftl
new file mode 100644
index 00000000000..ad58e436b39
--- /dev/null
+++ b/Resources/Locale/en-US/administration/ui/tabs/babyjail-tab.ftl
@@ -0,0 +1,16 @@
+admin-ui-baby-jail-window-title = Baby Jail
+
+admin-ui-baby-jail-enabled = Baby Jail Enabled
+admin-ui-baby-jail-disabled = Baby Jail Disabled
+admin-ui-baby-jail-tooltip = The baby jail restricts players from joining if their account is too old or they do have too much overall playtime on this server.
+
+admin-ui-baby-jail-show-reason = Show Reason
+admin-ui-baby-jail-show-reason-tooltip = Show the user why they were blocked from connecting by the baby jail.
+
+admin-ui-baby-jail-max-account-age = Max. Account Age
+admin-ui-baby-jail-max-overall-minutes = Max. Overall Playtime
+
+admin-ui-baby-jail-is-enabled = The baby jail is currently enabled.
+
+admin-ui-baby-jail-enabled-admin-alert = The baby jail has been enabled.
+admin-ui-baby-jail-disabled-admin-alert = The baby jail has been disabled.
diff --git a/Resources/Locale/en-US/connection-messages.ftl b/Resources/Locale/en-US/connection-messages.ftl
index 7f4afce8b96..88b6442e395 100644
--- a/Resources/Locale/en-US/connection-messages.ftl
+++ b/Resources/Locale/en-US/connection-messages.ftl
@@ -39,3 +39,9 @@ panic-bunker-account-denied = This server is in panic bunker mode, often enabled
panic-bunker-account-denied-reason = This server is in panic bunker mode, often enabled as a precaution against raids. New connections by accounts not meeting certain requirements are temporarily not accepted. Try again later. Reason: "{$reason}"
panic-bunker-account-reason-account = Your Space Station 14 account is too new. It must be older than {$minutes} minutes
panic-bunker-account-reason-overall = Your overall playtime on the server must be greater than {$hours} hours
+
+baby-jail-account-denied = This server is a newbie server, intended for new players and those who want to help them. New connections by accounts that are too old or are not on a whitelist are not accepted. Check out some other servers and see everything Space Station 14 has to offer. Have fun!
+baby-jail-account-denied-reason = This server is a newbie server, intended for new players and those who want to help them. New connections by accounts that are too old or are not on a whitelist are not accepted. Check out some other servers and see everything Space Station 14 has to offer. Have fun! Reason: "{$reason}"
+baby-jail-account-reason-account = Your Space Station 14 account is too old. It must be younger than {$minutes} minutes
+baby-jail-account-reason-overall = Your overall playtime on the server must be younger than {$hours} hours
+
diff --git a/Resources/Locale/en-US/generic.ftl b/Resources/Locale/en-US/generic.ftl
index d9c1305e416..35040978852 100644
--- a/Resources/Locale/en-US/generic.ftl
+++ b/Resources/Locale/en-US/generic.ftl
@@ -10,6 +10,7 @@ generic-error = error
generic-invalid = invalid
generic-hours = hours
+generic-minutes = minutes
generic-playtime-title = Playtime