Skip to content

Commit

Permalink
Port Queue and Discord Auth From Corvax (#355)
Browse files Browse the repository at this point in the history
# Description

I didn't make 2 PRs because the queue kinda depends on Discord auth and
I didn't wanna spend the time splitting them up.

The queue puts everyone except admins/whitelisted/previously in-game
players into a queue after the soft max players is reached, hard cap
still denies new connections (and queues) from everyone.
Priority queuing is simple to add and I'll do that when I make donator
benefits or whatever similar system.

---

<details><summary><h1>Media</h1></summary>
<p>

Too big to embed and I don't wanna compress it :)

https://youtu.be/NBqN6Piv94w

</p>
</details>

---

# Changelog

:cl:
- add: Added a queue for players so you don't need to spam reconnect and
hope you join when someone leaves.
  • Loading branch information
DEATHB4DEFEAT authored May 13, 2024
1 parent e090fa3 commit d4663e9
Show file tree
Hide file tree
Showing 23 changed files with 731 additions and 12 deletions.
28 changes: 28 additions & 0 deletions Content.Client/DiscordAuth/DiscordAuthGui.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Control xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:parallax="clr-namespace:Content.Client.Parallax"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
<parallax:ParallaxControl />
<Control HorizontalAlignment="Center" VerticalAlignment="Center">
<PanelContainer StyleClasses="AngleRect" />
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Margin="8 0 0 0" Text="{Loc 'discord-auth-title'}"
StyleClasses="LabelHeading" VAlign="Center" />
<Button Name="QuitButton" Text="{Loc 'discord-auth-quit-btn'}"
HorizontalAlignment="Right" HorizontalExpand="True" />
</BoxContainer>
<controls:HighDivider />
<BoxContainer Orientation="Vertical" Margin="50 20 50 20">
<Label Text="{Loc 'discord-auth-info'}" Align="Center" />
<Label Text="{Loc 'discord-auth-warn'}" Margin="0 5 0 0" Align="Center" StyleClasses="LabelSubText" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" VerticalAlignment="Bottom" Margin="10 0 0 0">
<Label Text="{Loc 'discord-auth-link'}" Align="Center" />
<Label Text=" " />
<LineEdit Name="UrlEdit" HorizontalExpand="True" Editable="False"></LineEdit>
</BoxContainer>
<Button Name="OpenUrlButton" Text="{Loc 'discord-auth-browser-btn'}" HorizontalExpand="True" StyleClasses="OpenRight" />
</BoxContainer>
</Control>
</Control>
36 changes: 36 additions & 0 deletions Content.Client/DiscordAuth/DiscordAuthGui.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;

namespace Content.Client.DiscordAuth;

[GenerateTypedNameReferences]
public sealed partial class DiscordAuthGui : Control
{
[Dependency] private readonly DiscordAuthManager _discordAuth = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;


public DiscordAuthGui()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide);

QuitButton.OnPressed += (_) =>
{
_consoleHost.ExecuteCommand("quit");
};

UrlEdit.Text = _discordAuth.AuthUrl;
OpenUrlButton.OnPressed += (_) =>
{
if (_discordAuth.AuthUrl != string.Empty)
{
IoCManager.Resolve<IUriOpener>().OpenUri(_discordAuth.AuthUrl);
}
};
}
}
31 changes: 31 additions & 0 deletions Content.Client/DiscordAuth/DiscordAuthManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Content.Shared.DiscordAuth;
using Robust.Client.State;
using Robust.Shared.Network;

namespace Content.Client.DiscordAuth;

public sealed class DiscordAuthManager
{
[Dependency] private readonly IClientNetManager _net = default!;
[Dependency] private readonly IStateManager _state = default!;


public string AuthUrl { get; private set; } = string.Empty;


public void Initialize()
{
_net.RegisterNetMessage<DiscordAuthCheckMessage>();
_net.RegisterNetMessage<DiscordAuthRequiredMessage>(OnDiscordAuthRequired);
}


private void OnDiscordAuthRequired(DiscordAuthRequiredMessage message)
{
if (_state.CurrentState is not DiscordAuthState)
{
AuthUrl = message.AuthUrl;
_state.RequestStateChange<DiscordAuthState>();
}
}
}
36 changes: 36 additions & 0 deletions Content.Client/DiscordAuth/DiscordAuthState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Threading;
using Content.Shared.DiscordAuth;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Shared.Network;
using Timer = Robust.Shared.Timing.Timer;

namespace Content.Client.DiscordAuth;

public sealed class DiscordAuthState : State
{
[Dependency] private readonly IUserInterfaceManager _userInterface = default!;
[Dependency] private readonly IClientNetManager _net = default!;


private DiscordAuthGui? _gui;
private readonly CancellationTokenSource _checkTimerCancel = new();


protected override void Startup()
{
_gui = new DiscordAuthGui();
_userInterface.StateRoot.AddChild(_gui);

Timer.SpawnRepeating(TimeSpan.FromSeconds(5), () =>
{
_net.ClientSendMessage(new DiscordAuthCheckMessage());
}, _checkTimerCancel.Token);
}

protected override void Shutdown()
{
_checkTimerCancel.Cancel();
_gui!.Dispose();
}
}
6 changes: 6 additions & 0 deletions Content.Client/Entry/EntryPoint.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using Content.Client.Administration.Managers;
using Content.Client.Changelog;
using Content.Client.Chat.Managers;
using Content.Client.DiscordAuth;
using Content.Client.JoinQueue;
using Content.Client.Eui;
using Content.Client.Flash;
using Content.Client.Fullscreen;
Expand Down Expand Up @@ -70,6 +72,8 @@ public sealed class EntryPoint : GameClient
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly IReplayLoadManager _replayLoad = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly JoinQueueManager _joinQueue = default!;
[Dependency] private readonly DiscordAuthManager _discordAuth = default!;

public override void Init()
{
Expand Down Expand Up @@ -162,6 +166,8 @@ public override void PostInit()
_userInterfaceManager.SetDefaultTheme("SS14DefaultTheme");
_userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme));
_documentParsingManager.Initialize();
_joinQueue.Initialize();
_discordAuth.Initialize();

_baseClient.RunLevelChanged += (_, args) =>
{
Expand Down
4 changes: 4 additions & 0 deletions Content.Client/IoC/ClientContentIoC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Content.Client.Changelog;
using Content.Client.Chat.Managers;
using Content.Client.Clickable;
using Content.Client.DiscordAuth;
using Content.Client.JoinQueue;
using Content.Client.Options;
using Content.Client.Eui;
using Content.Client.GhostKick;
Expand Down Expand Up @@ -49,6 +51,8 @@ public static void Register()
IoCManager.Register<JobRequirementsManager>();
IoCManager.Register<DocumentParsingManager>();
IoCManager.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
IoCManager.Register<JoinQueueManager>();
IoCManager.Register<DiscordAuthManager>();
}
}
}
26 changes: 26 additions & 0 deletions Content.Client/JoinQueue/JoinQueueManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Content.Shared.JoinQueue;
using Robust.Client.State;
using Robust.Shared.Network;

namespace Content.Client.JoinQueue;

public sealed class JoinQueueManager
{
[Dependency] private readonly IClientNetManager _net = default!;
[Dependency] private readonly IStateManager _state = default!;


public void Initialize()
{
_net.RegisterNetMessage<QueueUpdateMessage>(OnQueueUpdate);
}


private void OnQueueUpdate(QueueUpdateMessage msg)
{
if (_state.CurrentState is not QueueState)
_state.RequestStateChange<QueueState>();

((QueueState) _state.CurrentState).OnQueueUpdate(msg);
}
}
31 changes: 31 additions & 0 deletions Content.Client/JoinQueue/QueueGui.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Control xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:parallax="clr-namespace:Content.Client.Parallax"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
<parallax:ParallaxControl />
<Control HorizontalAlignment="Center" VerticalAlignment="Center">
<PanelContainer StyleClasses="AngleRect" />
<BoxContainer Orientation="Vertical" MinSize="200 200">
<BoxContainer Orientation="Horizontal">
<Label Margin="8 0 0 0" Text="{Loc 'queue-title'}"
StyleClasses="LabelHeading" VAlign="Center" />
<Button Name="QuitButton" Text="{Loc 'queue-quit'}"
HorizontalAlignment="Right" HorizontalExpand="True" />
</BoxContainer>
<controls:HighDivider />
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="0 20 0 0">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<Label Text="{Loc 'queue-position'}" Align="Center" />
<Label Name="QueuePosition" StyleClasses="LabelHeading" Align="Center" />
</BoxContainer>
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="0 10 0 0">
<Label Text="{Loc 'queue-total'}" Align="Center" />
<Label Name="QueueTotal" StyleClasses="LabelHeading" Align="Center" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
</BoxContainer>
</Control>
</Control>
29 changes: 29 additions & 0 deletions Content.Client/JoinQueue/QueueGui.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;

namespace Content.Client.JoinQueue;

[GenerateTypedNameReferences]
public sealed partial class QueueGui : Control
{
public event Action? QuitPressed;


public QueueGui()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide);

QuitButton.OnPressed += (_) => QuitPressed?.Invoke();
}


public void UpdateInfo(int total, int position)
{
QueueTotal.Text = total.ToString();
QueuePosition.Text = position.ToString();
}
}
54 changes: 54 additions & 0 deletions Content.Client/JoinQueue/QueueState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Content.Shared.JoinQueue;
using Robust.Client.Audio;
using Robust.Client.Console;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Shared.Player;

namespace Content.Client.JoinQueue;

public sealed class QueueState : State
{
[Dependency] private readonly IUserInterfaceManager _userInterface = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;


private const string JoinSoundPath = "/Audio/Effects/newplayerping.ogg";

private QueueGui? _gui;


protected override void Startup()
{
_gui = new QueueGui();
_userInterface.StateRoot.AddChild(_gui);

_gui.QuitPressed += OnQuitPressed;
}

protected override void Shutdown()
{
_gui!.QuitPressed -= OnQuitPressed;
_gui.Dispose();

Ding();
}


public void OnQueueUpdate(QueueUpdateMessage msg)
{
_gui?.UpdateInfo(msg.Total, msg.Position);
}

private void OnQuitPressed()
{
_console.ExecuteCommand("quit");
}


private void Ding()
{
if (IoCManager.Resolve<IEntityManager>().TrySystem<AudioSystem>(out var audio))
audio.PlayGlobal(JoinSoundPath, Filter.Local(), false);
}
}
22 changes: 16 additions & 6 deletions Content.Server/Connection/ConnectionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace Content.Server.Connection
public interface IConnectionManager
{
void Initialize();
Task<bool> HasPrivilegedJoin(NetUserId userId);
}

/// <summary>
Expand Down Expand Up @@ -157,13 +158,13 @@ private async Task NetMgrOnConnecting(NetConnectingArgs e)
}
}

var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
status == PlayerGameStatus.JoinedGame;
if ((_plyMgr.PlayerCount >= _cfg.GetCVar(CCVars.SoftMaxPlayers) && adminData is null) && !wasInGame)
{

var isPrivileged = await HasPrivilegedJoin(userId);
var isQueueEnabled = _cfg.GetCVar(CCVars.QueueEnabled);

if (_plyMgr.PlayerCount >= _cfg.GetCVar(CCVars.SoftMaxPlayers) && !isPrivileged && !isQueueEnabled)
return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null);
}


var bans = await _db.GetServerBansAsync(addr, userId, hwId, includeUnbanned: false);
if (bans.Count > 0)
Expand Down Expand Up @@ -259,5 +260,14 @@ private async void OnDisconnected(object? sender, NetChannelArgs e)
_connectedWhitelistedPlayers.Remove(e.Channel.UserId);
}
}

public async Task<bool> HasPrivilegedJoin(NetUserId userId)
{
var isAdmin = await _dbManager.GetAdminDataForAsync(userId) != null;
var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
status == PlayerGameStatus.JoinedGame;
return isAdmin || wasInGame;
}
}
}
Loading

0 comments on commit d4663e9

Please sign in to comment.