This commit is contained in:
KloopRE 2026-09-23 22:54:16 +03:00 • committed by GitHub
parent 19e887f64c
commit a04b0d8ab2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 241 additions and 77 deletions

View file

@ -0,0 +1,86 @@
using Content.Shared.Corvax.CCCVars;
using Content.Shared.Corvax.Events;
using Content.Shared.Corvax.Ghost;
using Robust.Client.Player;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Configuration;
using Robust.Shared.Timing;
namespace Content.Client.Corvax.Ghost;
public sealed partial class GhostGoLobbyButton : Button
{
[Dependency] private IEntityManager _entMan = default!;
[Dependency] private IPlayerManager _players = default!;
[Dependency] private IEntityNetworkManager _net = default!;
[Dependency] private IConfigurationManager _cfg = default!;
[Dependency] private IGameTiming _timing = default!;
private static readonly Color Waiting = Color.FromHex("#8B2E2E");
private static readonly Color Ready = Color.FromHex("#2E8B3E");
private GhostGoLobbyConfirmWindow? _confirmWindow;
public GhostGoLobbyButton()
{
IoCManager.InjectDependencies(this);
OnPressed += _ => OnPress();
}
protected override void EnteredTree()
{
base.EnteredTree();
_cfg.OnValueChanged(CCCVars.GhostGoLobbyEnabled, OnEnabledChanged, true);
}
protected override void ExitedTree()
{
_cfg.UnsubValueChanged(CCCVars.GhostGoLobbyEnabled, OnEnabledChanged);
_confirmWindow?.Close();
base.ExitedTree();
}
private void OnEnabledChanged(bool enabled)
{
Visible = enabled;
}
private void OnPress()
{
if (_confirmWindow is { Disposed: false })
{
_confirmWindow.MoveToFront();
return;
}
_confirmWindow = new GhostGoLobbyConfirmWindow();
_confirmWindow.ContinuePressed += () => _net.SendSystemNetworkMessage(new GhostGoLobbyEvent());
_confirmWindow.OnClose += () => _confirmWindow = null;
_confirmWindow.OpenCentered();
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
var remaining = _players.LocalEntity is { } player
&& _entMan.TryGetComponent<GhostGoLobbyComponent>(player, out var lobby)
? lobby.AvailableAt - _timing.CurTime
: TimeSpan.Zero;
if (remaining > TimeSpan.Zero)
{
var text = $"{(int)remaining.TotalMinutes:D2}:{remaining.Seconds:D2}";
Text = Loc.GetString("ghost-go-lobby-button-waiting", ("time", text));
Disabled = true;
ModulateSelfOverride = Waiting;
}
else
{
Text = Loc.GetString("ghost-go-lobby-button");
Disabled = false;
ModulateSelfOverride = Ready;
}
}
}

View file

@ -1,5 +1,4 @@
using Content.Client.Movement.Systems;
using Content.Shared.Corvax.Events;
using Content.Shared.Actions;
using Content.Shared.Ghost.Components;
using Content.Shared.Ghost.Systems;
@ -188,11 +187,6 @@ namespace Content.Client.Ghost
RaiseNetworkEvent(new GhostWarpsRequestEvent());
}
public void GhostGoLobby() // Corvax-GoLobby
{
RaiseNetworkEvent(new GhostGoLobbyEvent());
}
public void ReturnToBody()
{
var msg = new GhostReturnToBodyRequest();

View file

@ -1,14 +1,11 @@
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;
@ -16,15 +13,11 @@ namespace Content.Client.UserInterface.Systems.Ghost;
public sealed partial class GhostUIController : UIController, IOnSystemChanged<GhostSystem>
{
[Dependency] private IEntityNetworkManager _net = default!;
[Dependency] private IConfigurationManager _cfg = default!; // Corvax-GoLobby
[UISystemDependency] private readonly GhostSystem? _system = default;
private GhostGui? Gui => UIManager.GetActiveUIWidgetOrNull<GhostGui>();
private GhostGoLobbyConfirmWindow? _goLobbyConfirmWindow; // Corvax-GoLobby
private bool _goLobbyEnabled = true; // Corvax-GoLobby
public override void Initialize()
{
base.Initialize();
@ -32,15 +25,6 @@ public sealed partial class GhostUIController : UIController, IOnSystemChanged<G
var gameplayStateLoad = UIManager.GetUIController<GameplayStateLoadController>();
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()
@ -81,7 +65,7 @@ public sealed partial class GhostUIController : UIController, IOnSystemChanged<G
}
Gui.Visible = _system?.IsGhost ?? false;
Gui.Update(_system?.AvailableGhostRoleCount, _system?.Player?.CanReturnToBody, _goLobbyEnabled); // Corvax-GoLobby edit
Gui.Update(_system?.AvailableGhostRoleCount, _system?.Player?.CanReturnToBody);
}
private void OnPlayerRemoved(GhostComponent component)
@ -158,7 +142,6 @@ public sealed partial class GhostUIController : UIController, IOnSystemChanged<G
Gui.TargetWindow.OnGhostnadoClicked += OnGhostnadoClicked;
Gui.TargetWindow.OnWarpToRandomFollowedClicked += OnWarpToRandomFollowedClicked;
Gui.TargetWindow.OnWarpToRandomClicked += OnWarpToRandomClicked;
Gui.GhostGoLobbyPressed += GhostGoLobby; // Corvax-GoLobby
UpdateGui();
}
@ -172,7 +155,6 @@ public sealed partial class GhostUIController : UIController, IOnSystemChanged<G
Gui.ReturnToBodyPressed -= ReturnToBody;
Gui.GhostRolesPressed -= GhostRolesPressed;
Gui.TargetWindow.WarpClicked -= OnWarpClicked;
Gui.GhostGoLobbyPressed -= GhostGoLobby; // Corvax-GoLobby
Gui.Hide();
}
@ -182,22 +164,6 @@ public sealed partial class GhostUIController : UIController, IOnSystemChanged<G
_system?.ReturnToBody();
}
// Corvax-Changes-Start
private void GhostGoLobby()
{
if (_goLobbyConfirmWindow is { Disposed: false })
{
_goLobbyConfirmWindow.MoveToFront();
return;
}
_goLobbyConfirmWindow = new GhostGoLobbyConfirmWindow();
_goLobbyConfirmWindow.ContinuePressed += () => _system?.GhostGoLobby();
_goLobbyConfirmWindow.OnClose += () => _goLobbyConfirmWindow = null;
_goLobbyConfirmWindow.OpenCentered();
}
// Corvax-Changes-End
private void RequestWarps()
{
_system?.RequestWarps();

View file

@ -1,11 +1,11 @@
<widgets:GhostGui xmlns="https://spacestation14.io"
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Ghost.Widgets"
xmlns:corvax="clr-namespace:Content.Client.Corvax.Ghost"
HorizontalAlignment="Center">
<BoxContainer Orientation="Horizontal">
<Button Name="ReturnToBodyButton" Text="{Loc ghost-gui-return-to-body-button}" />
<Button Name="GhostWarpButton" Text="{Loc ghost-gui-ghost-warp-button}" />
<Button Name="GhostRolesButton" />
<!-- Corvax-Changes: Added GhostGoLobbyButton -->
<Button Name="GhostGoLobbyButton" Text="{Loc ghost-go-lobby-button}" />
<corvax:GhostGoLobbyButton /> <!-- Corvax-GoLobby -->
</BoxContainer>
</widgets:GhostGui>

View file

@ -14,7 +14,6 @@ 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()
@ -29,7 +28,6 @@ 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()
@ -38,10 +36,9 @@ public sealed partial class GhostGui : UIWidget
Visible = false;
}
public void Update(int? roles, bool? canReturnToBody, bool? canGoLobby = true) // Corvax-GoLobby edit
public void Update(int? roles, bool? canReturnToBody)
{
ReturnToBodyButton.Disabled = !canReturnToBody ?? true;
GhostGoLobbyButton.Visible = canGoLobby ?? true; // Corvax-GoLobby
if (roles != null)
{

View file

@ -0,0 +1,34 @@
using System.Globalization;
using Content.Server.Administration;
using Content.Shared.Administration;
using Content.Shared.Corvax.CCCVars;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
namespace Content.Server.Corvax.Ghost;
[AdminCommand(AdminFlags.Server)]
public sealed partial class GhostGoLobbyDeathTimeCommand : LocalizedCommands
{
[Dependency] private IConfigurationManager _cfg = default!;
public override string Command => "ghost_golobby_deathtime";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteError(Loc.GetString("shell-need-exactly-one-argument"));
return;
}
if (!float.TryParse(args[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var minutes))
{
shell.WriteError(Help);
return;
}
_cfg.SetCVar(CCCVars.GhostGoLobbyDeathTimeMinutes, minutes);
shell.WriteLine($"ghost.go_lobby.death_time = {minutes}");
}
}

View file

@ -0,0 +1,33 @@
using Content.Server.Administration;
using Content.Shared.Administration;
using Content.Shared.Corvax.CCCVars;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
namespace Content.Server.Corvax.Ghost;
[AdminCommand(AdminFlags.Server)]
public sealed partial class GhostGoLobbyEnabledCommand : LocalizedCommands
{
[Dependency] private IConfigurationManager _cfg = default!;
public override string Command => "ghost_golobby";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteError(Loc.GetString("shell-need-exactly-one-argument"));
return;
}
if (!bool.TryParse(args[0], out var enabled))
{
shell.WriteError(Loc.GetString("shell-argument-must-be-boolean"));
return;
}
_cfg.SetCVar(CCCVars.GhostGoLobbyEnabled, enabled);
shell.WriteLine($"ghost.go_lobby.enabled = {enabled}");
}
}

View file

@ -1,18 +1,15 @@
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.Corvax.Ghost;
using Content.Shared.GameTicking;
using Content.Shared.Ghost.Components;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Popups;
using Robust.Server.Player;
using Content.Shared.Preferences;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Timing;
@ -25,39 +22,69 @@ public sealed partial class GhostGoLobbySystem : EntitySystem
[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<NetUserId, HashSet<int>> _usedCharacterSlots = new();
private readonly HashSet<int> _usedCharacters = new();
public int GetSelectedSlot(NetUserId userId)
public bool IsCharacterUsed(HumanoidCharacterProfile profile)
{
return _prefsManager.GetPreferences(userId).SelectedCharacterIndex;
return _usedCharacters.Contains(GetCharacterHash(profile));
}
public bool CanUseCharacter(NetUserId userId, int characterSlot)
public void MarkCharacterUsed(HumanoidCharacterProfile profile)
{
return !_usedCharacterSlots.TryGetValue(userId, out var slots) || !slots.Contains(characterSlot);
_usedCharacters.Add(GetCharacterHash(profile));
}
private static int GetCharacterHash(HumanoidCharacterProfile profile)
{
return HashCode.Combine(profile.Name, profile.Sex, profile.Age, profile.Species);
}
public override void Initialize()
{
SubscribeNetworkEvent<GhostGoLobbyEvent>(OnGhostGoLobby);
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRunLevelChanged);
SubscribeLocalEvent<GhostComponent, PlayerAttachedEvent>(OnGhostAttached);
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);
Subs.CVar(_cfg, CCCVars.GhostGoLobbyDeathTimeMinutes, OnDeathTimeChanged, true);
}
private void OnDeathTimeChanged(float minutes)
{
_deathTime = TimeSpan.FromMinutes(minutes);
var clampTo = _timing.CurTime + _deathTime;
var query = EntityQueryEnumerator<GhostGoLobbyComponent>();
while (query.MoveNext(out var uid, out var lobby))
{
if (lobby.AvailableAt <= clampTo)
continue;
lobby.AvailableAt = clampTo;
Dirty(uid, lobby);
}
}
private void OnRunLevelChanged(GameRunLevelChangedEvent ev)
{
if (ev.New == GameRunLevel.PreRoundLobby)
_usedCharacterSlots.Clear();
_usedCharacters.Clear();
}
private void OnGhostAttached(EntityUid uid, GhostComponent component, PlayerAttachedEvent args)
{
if (HasComp<GhostGoLobbyComponent>(uid))
return;
var lobby = AddComp<GhostGoLobbyComponent>(uid);
lobby.AvailableAt = _timing.CurTime + _deathTime;
Dirty(uid, lobby);
}
private void OnGhostGoLobby(GhostGoLobbyEvent msg, EntitySessionEventArgs args)
@ -76,28 +103,18 @@ public sealed partial class GhostGoLobbySystem : EntitySystem
var all = _playTime.GetOverallPlaytime(session);
if (all < _requiredPlaytime)
{
var remaining = (int) Math.Ceiling((_requiredPlaytime - all).TotalHours);
var remaining = (int)Math.Ceiling((_requiredPlaytime - all).TotalHours);
_popup.PopupEntity(Loc.GetString("ghost-go-lobby-playtime", ("hours", remaining)), uid, uid);
return;
}
if (!TryComp<GhostComponent>(uid, out var ghost))
return;
var timeSinceDeath = _timing.RealTime - ghost.TimeOfDeath;
if (_deathTime > TimeSpan.Zero && timeSinceDeath < _deathTime)
if (TryComp<GhostGoLobbyComponent>(uid, out var lobby) && _timing.CurTime < lobby.AvailableAt)
{
var remaining = (int) ((_deathTime - timeSinceDeath).TotalMinutes);
var remaining = (int)Math.Ceiling((lobby.AvailableAt - _timing.CurTime).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<int>();
slots.Add(slot);
_mind.WipeMind(session);
RaiseLocalEvent(new GhostJoinLobbyRequestEvent(session));

View file

@ -166,13 +166,14 @@ namespace Content.Server.GameTicking
return;
// Corvax-GoLobby-start
var selectedSlot = _ghostGoLobby.GetSelectedSlot(player.UserId);
if (!_ghostGoLobby.CanUseCharacter(player.UserId, selectedSlot))
if (_ghostGoLobby.IsCharacterUsed(character))
{
_chatManager.DispatchServerMessage(player, Loc.GetString("ghost-go-lobby-used"));
return;
}
// Corvax-GoLobby-end
var originalCharacter = character;
// Corvax-GoLobby-end
if (station == EntityUid.Invalid)
{
@ -236,6 +237,7 @@ namespace Content.Server.GameTicking
// Do nothing, something else has handled spawning this player for us!
if (bev.Handled)
{
_ghostGoLobby.MarkCharacterUsed(originalCharacter); // Corvax-GoLobby
PlayerJoinGame(player, silent);
return;
}
@ -270,6 +272,8 @@ namespace Content.Server.GameTicking
return;
}
_ghostGoLobby.MarkCharacterUsed(originalCharacter); // Corvax-GoLobby
DoSpawn(player, character, station, jobId, silent, out var mob, out var jobPrototype, out var jobName);
if (lateJoin && !silent)

View file

@ -142,12 +142,21 @@ public sealed class CCCVars
public static readonly CVarDef<bool> LobbyServerHubEnabled =
CVarDef.Create("lobby.server_hub_enabled", false, CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Установить режим возвраждения.
/// </summary>
public static readonly CVarDef<bool> GhostGoLobbyEnabled =
CVarDef.Create("ghost.go_lobby.enabled", false, CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Лимит по общему количеству наигранного времени для возвраждения.
/// </summary>
public static readonly CVarDef<float> GhostGoLobbyTimeHours =
CVarDef.Create("ghost.go_lobby.require_time", 25f, CVar.SERVERONLY);
/// <summary>
/// Длительность до возможность возврадиться.
/// </summary>
public static readonly CVarDef<float> GhostGoLobbyDeathTimeMinutes =
CVarDef.Create("ghost.go_lobby.death_time", 15f, CVar.SERVERONLY);

View file

@ -0,0 +1,10 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Corvax.Ghost;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class GhostGoLobbyComponent : Component
{
[DataField, AutoNetworkedField]
public TimeSpan AvailableAt;
}

View file

@ -1,4 +1,5 @@
ghost-go-lobby-button = Return to lobby
ghost-go-lobby-button-waiting = To lobby in { $time }
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.
@ -10,3 +11,9 @@ ghost-go-lobby-confirm = Are you sure you want to return to the lobby?
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
cmd-ghost_golobby-desc = Toggles the ghost "return to lobby" button (ghost.go_lobby.enabled).
cmd-ghost_golobby-help = ghost_golobby <true|false>
cmd-ghost_golobby_deathtime-desc = Sets the return-to-lobby delay in minutes (ghost.go_lobby.death_time). Shortens the timer for already waiting ghosts.
cmd-ghost_golobby_deathtime-help = ghost_golobby_deathtime <minutes>

View file

@ -1,4 +1,5 @@
ghost-go-lobby-button = Вернуться в лобби
ghost-go-lobby-button-waiting = В лобби через { $time }
ghost-go-lobby-deathtime = Вернуться в лобби можно через {$minutes} мин.
ghost-go-lobby-playtime = Вам нужно наиграть еще {$hours} ч.
@ -10,3 +11,9 @@ ghost-go-lobby-confirm = Вы уверены, что хотите вернуть
Вам разрешается помнить общие знания об игре: например, как готовить, использовать предметы и т.д.
Вам [color=red]НЕ[/color] разрешается помнить или говорить имя, внешность и т.д. вашего предыдущего персонажа.
ghost-go-lobby-confirm-continue = Продолжить
cmd-ghost_golobby-desc = Переключает кнопку призрака "вернуться в лобби" (ghost.go_lobby.enabled).
cmd-ghost_golobby-help = ghost_golobby <true|false>
cmd-ghost_golobby_deathtime-desc = Задаёт задержку возврата в лобби в минутах (ghost.go_lobby.death_time). Укорачивает таймер у уже ждущих призраков.
cmd-ghost_golobby_deathtime-help = ghost_golobby_deathtime <минуты>