From eacc62bbafe9dd02a82fb45adde32bcb482747e1 Mon Sep 17 00:00:00 2001
From: shimorio <124311259+shimorio@users.noreply.github.com>
Date: Thu, 3 Sep 2026 22:31:15 +0300
Subject: [PATCH] golobby for vanilla (#3715)
---
.../Ghost/GhostGoLobbyConfirmWindow.xaml | 15 +++
.../Ghost/GhostGoLobbyConfirmWindow.xaml.cs | 25 +++++
Content.Client/Ghost/GhostSystem.cs | 6 +
.../Systems/Ghost/GhostUIController.cs | 36 +++++-
.../Systems/Ghost/Widgets/GhostGui.xaml | 4 +-
.../Systems/Ghost/Widgets/GhostGui.xaml.cs | 5 +-
.../Events/GhostJoinLobbyRequestEvent.cs | 13 +++
.../Corvax/Ghost/GhostGoLobbySystem.cs | 105 ++++++++++++++++++
.../GameTicking/GameTicker.Player.cs | 9 ++
.../GameTicking/GameTicker.Spawning.cs | 9 ++
Content.Server/GameTicking/GameTicker.cs | 2 +
Content.Shared/Corvax/CCCVars/CCCVars.cs | 12 +-
.../Corvax/Events/GhostGoLobbyEvent.cs | 6 +
Resources/ConfigPresets/Corvax/mrp.toml | 3 +
.../corvax/ghost/golobby/ghost-go-lobby.ftl | 12 ++
.../corvax/ghost/golobby/ghost-go-lobby.ftl | 12 ++
16 files changed, 270 insertions(+), 4 deletions(-)
create mode 100644 Content.Client/Corvax/Ghost/GhostGoLobbyConfirmWindow.xaml
create mode 100644 Content.Client/Corvax/Ghost/GhostGoLobbyConfirmWindow.xaml.cs
create mode 100644 Content.Server/Corvax/Events/GhostJoinLobbyRequestEvent.cs
create mode 100644 Content.Server/Corvax/Ghost/GhostGoLobbySystem.cs
create mode 100644 Content.Shared/Corvax/Events/GhostGoLobbyEvent.cs
create mode 100644 Resources/Locale/en-US/corvax/ghost/golobby/ghost-go-lobby.ftl
create mode 100644 Resources/Locale/ru-RU/corvax/ghost/golobby/ghost-go-lobby.ftl
diff --git a/Content.Client/Corvax/Ghost/GhostGoLobbyConfirmWindow.xaml b/Content.Client/Corvax/Ghost/GhostGoLobbyConfirmWindow.xaml
new file mode 100644
index 0000000000..a00cba8b25
--- /dev/null
+++ b/Content.Client/Corvax/Ghost/GhostGoLobbyConfirmWindow.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Corvax/Ghost/GhostGoLobbyConfirmWindow.xaml.cs b/Content.Client/Corvax/Ghost/GhostGoLobbyConfirmWindow.xaml.cs
new file mode 100644
index 0000000000..e867eecbe8
--- /dev/null
+++ b/Content.Client/Corvax/Ghost/GhostGoLobbyConfirmWindow.xaml.cs
@@ -0,0 +1,25 @@
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Corvax.Ghost;
+
+[GenerateTypedNameReferences]
+public sealed partial class GhostGoLobbyConfirmWindow : FancyWindow
+{
+ public event Action? ContinuePressed;
+
+ public GhostGoLobbyConfirmWindow()
+ {
+ RobustXamlLoader.Load(this);
+
+ WarningLabel.SetMessage(FormattedMessage.FromMarkupPermissive(Loc.GetString("ghost-go-lobby-confirm")));
+
+ ContinueButton.OnPressed += _ =>
+ {
+ ContinuePressed?.Invoke();
+ Close();
+ };
+ }
+}
diff --git a/Content.Client/Ghost/GhostSystem.cs b/Content.Client/Ghost/GhostSystem.cs
index 3d65e993be..3bb6738f49 100644
--- a/Content.Client/Ghost/GhostSystem.cs
+++ b/Content.Client/Ghost/GhostSystem.cs
@@ -1,4 +1,5 @@
using Content.Client.Movement.Systems;
+using Content.Shared.Corvax.Events;
using Content.Shared.Actions;
using Content.Shared.Ghost.Components;
using Content.Shared.Ghost.Systems;
@@ -187,6 +188,11 @@ namespace Content.Client.Ghost
RaiseNetworkEvent(new GhostWarpsRequestEvent());
}
+ public void GhostGoLobby() // Corvax-GoLobby
+ {
+ RaiseNetworkEvent(new GhostGoLobbyEvent());
+ }
+
public void ReturnToBody()
{
var msg = new GhostReturnToBodyRequest();
diff --git a/Content.Client/UserInterface/Systems/Ghost/GhostUIController.cs b/Content.Client/UserInterface/Systems/Ghost/GhostUIController.cs
index db583b9875..a1306b6d5a 100644
--- a/Content.Client/UserInterface/Systems/Ghost/GhostUIController.cs
+++ b/Content.Client/UserInterface/Systems/Ghost/GhostUIController.cs
@@ -1,11 +1,14 @@
+using Content.Client.Corvax.Ghost;
using Content.Client.Gameplay;
using Content.Client.Ghost;
using Content.Client.UserInterface.Systems.Gameplay;
using Content.Client.UserInterface.Systems.Ghost.Widgets;
+using Content.Shared.Corvax.CCCVars;
using Content.Shared.Ghost.Components;
using Content.Shared.Ghost.Systems;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
+using Robust.Shared.Configuration;
namespace Content.Client.UserInterface.Systems.Ghost;
@@ -13,11 +16,15 @@ namespace Content.Client.UserInterface.Systems.Ghost;
public sealed partial class GhostUIController : UIController, IOnSystemChanged
{
[Dependency] private IEntityNetworkManager _net = default!;
+ [Dependency] private IConfigurationManager _cfg = default!; // Corvax-GoLobby
[UISystemDependency] private readonly GhostSystem? _system = default;
private GhostGui? Gui => UIManager.GetActiveUIWidgetOrNull();
+ private GhostGoLobbyConfirmWindow? _goLobbyConfirmWindow; // Corvax-GoLobby
+ private bool _goLobbyEnabled = true; // Corvax-GoLobby
+
public override void Initialize()
{
base.Initialize();
@@ -25,6 +32,15 @@ public sealed partial class GhostUIController : UIController, IOnSystemChanged();
gameplayStateLoad.OnScreenLoad += OnScreenLoad;
gameplayStateLoad.OnScreenUnload += OnScreenUnload;
+
+ // Corvax-GoLobby
+ _cfg.OnValueChanged(CCCVars.GhostGoLobbyEnabled, OnGoLobbyEnabledChanged, true);
+ }
+
+ private void OnGoLobbyEnabledChanged(bool enabled) // Corvax-GoLobby
+ {
+ _goLobbyEnabled = enabled;
+ UpdateGui();
}
private void OnScreenLoad()
@@ -65,7 +81,7 @@ public sealed partial class GhostUIController : UIController, IOnSystemChanged _system?.GhostGoLobby();
+ _goLobbyConfirmWindow.OnClose += () => _goLobbyConfirmWindow = null;
+ _goLobbyConfirmWindow.OpenCentered();
+ }
+ // Corvax-Changes-End
+
private void RequestWarps()
{
_system?.RequestWarps();
diff --git a/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml b/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml
index 0f65debb4e..44b934801f 100644
--- a/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml
+++ b/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml
@@ -1,9 +1,11 @@
-
+
+
diff --git a/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml.cs b/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml.cs
index 7252b17bf9..e78d433814 100644
--- a/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml.cs
@@ -14,6 +14,7 @@ public sealed partial class GhostGui : UIWidget
public event Action? RequestWarpsPressed;
public event Action? ReturnToBodyPressed;
public event Action? GhostRolesPressed;
+ public event Action? GhostGoLobbyPressed; // Corvax-GoLobby
private int _prevNumberRoles;
public GhostGui()
@@ -28,6 +29,7 @@ public sealed partial class GhostGui : UIWidget
ReturnToBodyButton.OnPressed += _ => ReturnToBodyPressed?.Invoke();
GhostRolesButton.OnPressed += _ => GhostRolesPressed?.Invoke();
GhostRolesButton.OnPressed += _ => GhostRolesButton.StyleClasses.Remove(StyleClass.Negative);
+ GhostGoLobbyButton.OnPressed += _ => GhostGoLobbyPressed?.Invoke(); // Corvax-GoLobby
}
public void Hide()
@@ -36,9 +38,10 @@ public sealed partial class GhostGui : UIWidget
Visible = false;
}
- public void Update(int? roles, bool? canReturnToBody)
+ public void Update(int? roles, bool? canReturnToBody, bool? canGoLobby = true) // Corvax-GoLobby edit
{
ReturnToBodyButton.Disabled = !canReturnToBody ?? true;
+ GhostGoLobbyButton.Visible = canGoLobby ?? true; // Corvax-GoLobby
if (roles != null)
{
diff --git a/Content.Server/Corvax/Events/GhostJoinLobbyRequestEvent.cs b/Content.Server/Corvax/Events/GhostJoinLobbyRequestEvent.cs
new file mode 100644
index 0000000000..e0512958b0
--- /dev/null
+++ b/Content.Server/Corvax/Events/GhostJoinLobbyRequestEvent.cs
@@ -0,0 +1,13 @@
+using Robust.Shared.Player;
+
+namespace Content.Server._Corvax.Events;
+
+public sealed class GhostJoinLobbyRequestEvent : EntityEventArgs
+{
+ public readonly ICommonSession Session;
+
+ public GhostJoinLobbyRequestEvent(ICommonSession session)
+ {
+ Session = session;
+ }
+}
diff --git a/Content.Server/Corvax/Ghost/GhostGoLobbySystem.cs b/Content.Server/Corvax/Ghost/GhostGoLobbySystem.cs
new file mode 100644
index 0000000000..4894ea6413
--- /dev/null
+++ b/Content.Server/Corvax/Ghost/GhostGoLobbySystem.cs
@@ -0,0 +1,105 @@
+using Content.Server._Corvax.Events;
+using Content.Server.Chat.Managers;
+using Content.Server.GameTicking;
+using Content.Server.Mind;
+using Content.Server.Players.PlayTimeTracking;
+using Content.Server.Preferences.Managers;
+using Content.Shared.Corvax.CCCVars;
+using Content.Shared.Corvax.Events;
+using Content.Shared.GameTicking;
+using Content.Shared.Ghost.Components;
+using Content.Shared.Players.PlayTimeTracking;
+using Content.Shared.Popups;
+using Robust.Server.Player;
+using Robust.Shared.Configuration;
+using Robust.Shared.Network;
+using Robust.Shared.Player;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Corvax.Ghost;
+
+public sealed partial class GhostGoLobbySystem : EntitySystem
+{
+ [Dependency] private IConfigurationManager _cfg = default!;
+ [Dependency] private IGameTiming _timing = default!;
+ [Dependency] private MindSystem _mind = default!;
+ [Dependency] private PlayTimeTrackingManager _playTime = default!;
+ [Dependency] private SharedPopupSystem _popup = default!;
+ [Dependency] private IChatManager _chatManager = default!;
+ [Dependency] private IServerPreferencesManager _prefsManager = default!;
+
+ private bool _enabled;
+ private TimeSpan _requiredPlaytime;
+ private TimeSpan _deathTime;
+
+ private readonly Dictionary> _usedCharacterSlots = new();
+
+ public int GetSelectedSlot(NetUserId userId)
+ {
+ return _prefsManager.GetPreferences(userId).SelectedCharacterIndex;
+ }
+
+ public bool CanUseCharacter(NetUserId userId, int characterSlot)
+ {
+ return !_usedCharacterSlots.TryGetValue(userId, out var slots) || !slots.Contains(characterSlot);
+ }
+
+ public override void Initialize()
+ {
+ SubscribeNetworkEvent(OnGhostGoLobby);
+ SubscribeLocalEvent(OnRunLevelChanged);
+
+ Subs.CVar(_cfg, CCCVars.GhostGoLobbyEnabled, value => _enabled = value, true);
+ Subs.CVar(_cfg, CCCVars.GhostGoLobbyTimeHours, value => _requiredPlaytime = TimeSpan.FromHours(value), true);
+ Subs.CVar(_cfg, CCCVars.GhostGoLobbyDeathTimeMinutes, value => _deathTime = TimeSpan.FromMinutes(value), true);
+ }
+
+ private void OnRunLevelChanged(GameRunLevelChangedEvent ev)
+ {
+ if (ev.New == GameRunLevel.PreRoundLobby)
+ _usedCharacterSlots.Clear();
+ }
+
+ private void OnGhostGoLobby(GhostGoLobbyEvent msg, EntitySessionEventArgs args)
+ {
+ if (args.SenderSession.AttachedEntity is not { Valid: true } attached)
+ return;
+
+ TryGhostGoLobby(attached, args.SenderSession);
+ }
+
+ private void TryGhostGoLobby(EntityUid uid, ICommonSession session)
+ {
+ if (!_enabled)
+ return;
+
+ var all = _playTime.GetOverallPlaytime(session);
+ if (all < _requiredPlaytime)
+ {
+ var remaining = (int) Math.Ceiling((_requiredPlaytime - all).TotalHours);
+ _popup.PopupEntity(Loc.GetString("ghost-go-lobby-playtime", ("hours", remaining)), uid, uid);
+ return;
+ }
+
+ if (!TryComp(uid, out var ghost))
+ return;
+
+ var timeSinceDeath = _timing.RealTime - ghost.TimeOfDeath;
+ if (_deathTime > TimeSpan.Zero && timeSinceDeath < _deathTime)
+ {
+ var remaining = (int) ((_deathTime - timeSinceDeath).TotalMinutes);
+ _popup.PopupEntity(Loc.GetString("ghost-go-lobby-deathtime", ("minutes", remaining)), uid, uid);
+ return;
+ }
+
+ var slot = GetSelectedSlot(session.UserId);
+ if (!_usedCharacterSlots.TryGetValue(session.UserId, out var slots))
+ _usedCharacterSlots[session.UserId] = slots = new HashSet();
+
+ slots.Add(slot);
+
+ _mind.WipeMind(session);
+
+ RaiseLocalEvent(new GhostJoinLobbyRequestEvent(session));
+ }
+}
diff --git a/Content.Server/GameTicking/GameTicker.Player.cs b/Content.Server/GameTicking/GameTicker.Player.cs
index 2816156c21..18ee32ca78 100644
--- a/Content.Server/GameTicking/GameTicker.Player.cs
+++ b/Content.Server/GameTicking/GameTicker.Player.cs
@@ -1,3 +1,4 @@
+using Content.Server._Corvax.Events;
using Content.Corvax.Interfaces.Server;
using Content.Shared.Administration;
using Content.Shared.CCVar;
@@ -24,6 +25,7 @@ namespace Content.Server.GameTicking
private void InitializePlayer()
{
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
+ SubscribeLocalEvent(OnGhostJoinLobbyRequest); // CorvaxGoob-GoLobby
}
private async void PlayerStatusChanged(object? sender, SessionStatusEventArgs args)
@@ -227,6 +229,13 @@ namespace Content.Server.GameTicking
RaiseLocalEvent(new PlayerJoinedLobbyEvent(session));
}
+ // Corvax-Changes-Start
+ private void OnGhostJoinLobbyRequest(GhostJoinLobbyRequestEvent ev)
+ {
+ PlayerJoinLobby(ev.Session);
+ }
+ // Corvax-Changes-End
+
private void ReqWindowAttentionAll()
{
RaiseNetworkEvent(new RequestWindowAttentionEvent());
diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs
index 72ebed0881..2942aea18d 100644
--- a/Content.Server/GameTicking/GameTicker.Spawning.cs
+++ b/Content.Server/GameTicking/GameTicker.Spawning.cs
@@ -165,6 +165,15 @@ namespace Content.Server.GameTicking
if (DummyTicker)
return;
+ // Corvax-GoLobby-start
+ var selectedSlot = _ghostGoLobby.GetSelectedSlot(player.UserId);
+ if (!_ghostGoLobby.CanUseCharacter(player.UserId, selectedSlot))
+ {
+ _chatManager.DispatchServerMessage(player, Loc.GetString("ghost-go-lobby-used"));
+ return;
+ }
+ // Corvax-GoLobby-end
+
if (station == EntityUid.Invalid)
{
var stations = GetSpawnableStations();
diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs
index 8b03b0c538..457a5eb210 100644
--- a/Content.Server/GameTicking/GameTicker.cs
+++ b/Content.Server/GameTicking/GameTicker.cs
@@ -5,6 +5,7 @@ using Content.Server.Chat.Managers;
using Content.Server.Chat.Systems;
using Content.Server.Database;
using Content.Server.Ghost;
+using Content.Server.Corvax.Ghost; // Corvax-GoLobby
using Content.Server.Maps;
using Content.Server.Players.PlayTimeTracking;
using Content.Server.Preferences.Managers;
@@ -50,6 +51,7 @@ namespace Content.Server.GameTicking
[Dependency] private MapLoaderSystem _loader = default!;
[Dependency] private SharedMapSystem _map = default!;
[Dependency] private GhostSystem _ghost = default!;
+ [Dependency] private GhostGoLobbySystem _ghostGoLobby = default!; // Corvax-GoLobby
[Dependency] private SharedMindSystem _mind = default!;
[Dependency] private PlayTimeTrackingSystem _playTimeTrackings = default!;
[Dependency] private PvsOverrideSystem _pvsOverride = default!;
diff --git a/Content.Shared/Corvax/CCCVars/CCCVars.cs b/Content.Shared/Corvax/CCCVars/CCCVars.cs
index f03e1c3d3d..9c98833717 100644
--- a/Content.Shared/Corvax/CCCVars/CCCVars.cs
+++ b/Content.Shared/Corvax/CCCVars/CCCVars.cs
@@ -1,4 +1,4 @@
-using Content.Shared.Corvax.TTS;
+using Content.Shared.Corvax.TTS;
using Robust.Shared.Configuration;
namespace Content.Shared.Corvax.CCCVars;
@@ -134,4 +134,14 @@ public sealed class CCCVars
///
public static readonly CVarDef StationGoal =
CVarDef.Create("game.station_goal", true, CVar.SERVERONLY);
+
+ public static readonly CVarDef GhostGoLobbyEnabled =
+ CVarDef.Create("ghost.go_lobby.enabled", false, CVar.SERVER | CVar.REPLICATED);
+
+ public static readonly CVarDef GhostGoLobbyTimeHours =
+ CVarDef.Create("ghost.go_lobby.require_time", 25f, CVar.SERVERONLY);
+
+ public static readonly CVarDef GhostGoLobbyDeathTimeMinutes =
+ CVarDef.Create("ghost.go_lobby.death_time", 15f, CVar.SERVERONLY);
+
}
diff --git a/Content.Shared/Corvax/Events/GhostGoLobbyEvent.cs b/Content.Shared/Corvax/Events/GhostGoLobbyEvent.cs
new file mode 100644
index 0000000000..918c1c3b11
--- /dev/null
+++ b/Content.Shared/Corvax/Events/GhostGoLobbyEvent.cs
@@ -0,0 +1,6 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Corvax.Events;
+
+[Serializable, NetSerializable]
+public sealed class GhostGoLobbyEvent : EntityEventArgs;
diff --git a/Resources/ConfigPresets/Corvax/mrp.toml b/Resources/ConfigPresets/Corvax/mrp.toml
index 21082d5338..3c38118897 100644
--- a/Resources/ConfigPresets/Corvax/mrp.toml
+++ b/Resources/ConfigPresets/Corvax/mrp.toml
@@ -27,3 +27,6 @@ same_type_timeout = 300.0
timerrestart = 30
preset_enabled = false
restart_enabled = false
+
+[ghost.go_lobby]
+enabled = true
diff --git a/Resources/Locale/en-US/corvax/ghost/golobby/ghost-go-lobby.ftl b/Resources/Locale/en-US/corvax/ghost/golobby/ghost-go-lobby.ftl
new file mode 100644
index 0000000000..175356665b
--- /dev/null
+++ b/Resources/Locale/en-US/corvax/ghost/golobby/ghost-go-lobby.ftl
@@ -0,0 +1,12 @@
+ghost-go-lobby-button = Return to lobby
+
+ghost-go-lobby-deathtime = You can return to the lobby in {$minutes} minutes.
+ghost-go-lobby-playtime = You need to play for another {$hours} hours.
+ghost-go-lobby-used = Please choose a different character.
+
+ghost-go-lobby-confirm-title = Returning to the Lobby
+ghost-go-lobby-confirm = Are you sure you want to return to the lobby?
+ You have forgotten everything about your current character and do not remember anything you learned while you were a ghost.
+ You are allowed to remember general knowledge about the game e.g., how to cook, use items, etc.
+ You are [color=red]NOT[/color] allowed to remember or mention the name, appearance, etc., of your previous character.
+ghost-go-lobby-confirm-continue = Continue
diff --git a/Resources/Locale/ru-RU/corvax/ghost/golobby/ghost-go-lobby.ftl b/Resources/Locale/ru-RU/corvax/ghost/golobby/ghost-go-lobby.ftl
new file mode 100644
index 0000000000..7885971bda
--- /dev/null
+++ b/Resources/Locale/ru-RU/corvax/ghost/golobby/ghost-go-lobby.ftl
@@ -0,0 +1,12 @@
+ghost-go-lobby-button = Вернуться в лобби
+
+ghost-go-lobby-deathtime = Вернуться в лобби можно через {$minutes} мин.
+ghost-go-lobby-playtime = Вам нужно наиграть еще {$hours} ч.
+ghost-go-lobby-used = Выберите другого персонажа.
+
+ghost-go-lobby-confirm-title = Возвращение в лобби
+ghost-go-lobby-confirm = Вы уверены, что хотите вернуться в лобби?
+ Вы забыли всё о своём текущем персонаже и ничего не помните из того, что узнали, будучи призраком.
+ Вам разрешается помнить общие знания об игре: например, как готовить, использовать предметы и т.д.
+ Вам [color=red]НЕ[/color] разрешается помнить или говорить имя, внешность и т.д. вашего предыдущего персонажа.
+ghost-go-lobby-confirm-continue = Продолжить