Хаб серверов (#13)

* Хаб серверов внутри игры

* чейнжлог
This commit is contained in:
Vigers Ray 2024-06-04 10:03:48 +03:00 committed by GitHub
parent c47264dfdb
commit e0566ffa3e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 594 additions and 72 deletions

View file

@ -1,3 +1,4 @@
using Content.Client._Sunrise.ServersHub;
using Content.Client.Administration.Managers;
using Content.Client.Changelog;
using Content.Client.Chat.Managers;
@ -71,6 +72,7 @@ namespace Content.Client.Entry
[Dependency] private readonly IReplayLoadManager _replayLoad = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly ContentReplayPlaybackManager _replayMan = default!;
[Dependency] private readonly ServersHubManager _serversHubManager = default!; // Sunrise-Hub
public override void Init()
{
@ -133,6 +135,8 @@ namespace Content.Client.Entry
_jobRequirements.Initialize();
_playbackMan.Initialize();
_serversHubManager.Initialize(); // Sunrise-Hub
//AUTOSCALING default Setup!
_configManager.SetCVar("interface.resolutionAutoScaleUpperCutoffX", 1080);
_configManager.SetCVar("interface.resolutionAutoScaleUpperCutoffY", 720);

View file

@ -1,4 +1,5 @@
using Content.Client.Administration.Managers;
using Content.Client._Sunrise.ServersHub;
using Content.Client.Administration.Managers;
using Content.Client.Changelog;
using Content.Client.Chat.Managers;
using Content.Client.Clickable;
@ -50,6 +51,10 @@ namespace Content.Client.IoC
collection.Register<DocumentParsingManager>();
collection.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
collection.Register<ISharedPlaytimeManager, JobRequirementsManager>();
// Sunrise
collection.Register<ServersHubManager>(); // Sunrise-Hub
}
}
}

View file

@ -2,54 +2,66 @@
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">
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:serversHub="clr-namespace:Content.Client._Sunrise.ServersHub">
<parallax:ParallaxControl />
<Control HorizontalAlignment="Center" VerticalAlignment="Center">
<PanelContainer StyleClasses="AngleRect" />
<BoxContainer Orientation="Vertical" MinSize="300 200">
<BoxContainer Orientation="Horizontal">
<Label Margin="8 0 0 0" Text="{Loc 'connecting-title'}"
StyleClasses="LabelHeading" VAlign="Center" />
<Button Name="ExitButton" Text="{Loc 'connecting-exit'}"
HorizontalAlignment="Right" HorizontalExpand="True" />
</BoxContainer>
<controls:HighDivider />
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="4 4 4 0">
<Control VerticalExpand="True" Margin="0 0 0 8">
<BoxContainer Orientation="Vertical" Name="ConnectingStatus">
<Label Text="{Loc 'connecting-in-progress'}" Align="Center" />
<!-- Who the fuck named these cont- oh wait I did -->
<Label Name="ConnectStatus" StyleClasses="LabelSubText" Align="Center" />
<BoxContainer Orientation="Vertical">
<PanelContainer StyleClasses="AngleRect">
<BoxContainer Orientation="Vertical" MinSize="300 200">
<BoxContainer Orientation="Horizontal">
<Label Margin="8 0 0 0" Text="{Loc 'connecting-title'}"
StyleClasses="LabelHeading" VAlign="Center" />
<Button Name="ExitButton" Text="{Loc 'connecting-exit'}"
HorizontalAlignment="Right" HorizontalExpand="True" />
</BoxContainer>
<BoxContainer Orientation="Vertical" Name="ConnectFail" Visible="False">
<RichTextLabel Name="ConnectFailReason" VerticalAlignment="Stretch"/>
<Button Name="RetryButton" Text="{Loc 'connecting-retry'}"
HorizontalAlignment="Center"
VerticalExpand="True" VerticalAlignment="Bottom" />
<controls:HighDivider />
<BoxContainer Orientation="Vertical" VerticalExpand="True" Margin="4 4 4 0">
<Control VerticalExpand="True" Margin="0 0 0 8">
<BoxContainer Orientation="Vertical" Name="ConnectingStatus">
<Label Text="{Loc 'connecting-in-progress'}" Align="Center" />
<!-- Who the fuck named these cont- oh wait I did -->
<Label Name="ConnectStatus" StyleClasses="LabelSubText" Align="Center" />
</BoxContainer>
<BoxContainer Orientation="Vertical" Name="ConnectFail" Visible="False">
<RichTextLabel Name="ConnectFailReason" VerticalAlignment="Stretch"/>
<Button Name="RetryButton" Text="{Loc 'connecting-retry'}"
HorizontalAlignment="Center"
VerticalExpand="True" VerticalAlignment="Bottom" />
</BoxContainer>
<BoxContainer Orientation="Vertical" Name="Disconnected">
<Label Text="{Loc 'connecting-disconnected'}" Align="Center" />
<Label Name="DisconnectReason" Align="Center" />
<Button Name="ReconnectButton" Text="{Loc 'connecting-reconnect'}"
HorizontalAlignment="Center"
VerticalExpand="True" VerticalAlignment="Bottom" />
</BoxContainer>
</Control>
<Label Name="ConnectingAddress" StyleClasses="LabelSubText" HorizontalAlignment="Center" />
</BoxContainer>
<BoxContainer Orientation="Vertical" Name="Disconnected">
<Label Text="{Loc 'connecting-disconnected'}" Align="Center" />
<Label Name="DisconnectReason" Align="Center" />
<Button Name="ReconnectButton" Text="{Loc 'connecting-reconnect'}"
HorizontalAlignment="Center"
VerticalExpand="True" VerticalAlignment="Bottom" />
</BoxContainer>
</Control>
<Label Name="ConnectingAddress" StyleClasses="LabelSubText" HorizontalAlignment="Center" />
</BoxContainer>
<PanelContainer>
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#444" ContentMarginTopOverride="2" />
</PanelContainer.PanelOverride>
</PanelContainer>
<PanelContainer>
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#444" ContentMarginTopOverride="2" />
</PanelContainer.PanelOverride>
</PanelContainer>
<BoxContainer Orientation="Horizontal" Margin="12 0 4 0" VerticalAlignment="Bottom">
<Label Text="{Loc 'connecting-tip'}" StyleClasses="LabelSubText" />
<Label Text="{Loc 'connecting-version'}" StyleClasses="LabelSubText"
HorizontalAlignment="Right" HorizontalExpand="True" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="12 0 4 0" VerticalAlignment="Bottom">
<Label Text="{Loc 'connecting-tip'}" StyleClasses="LabelSubText" />
<Label Text="{Loc 'connecting-version'}" StyleClasses="LabelSubText"
HorizontalAlignment="Right" HorizontalExpand="True" />
</BoxContainer>
</BoxContainer>
</PanelContainer>
<PanelContainer StyleClasses="AngleRect" HorizontalExpand="True" Margin="5 0 0 0" Name="ServersHub" MinWidth="600" MinHeight="220">
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="3 3 3 3">
<ScrollContainer VerticalExpand="True" HScrollEnabled="False" HorizontalExpand="True">
<serversHub:ServersHub Name="ServersHubBody" Access="Public" />
</ScrollContainer>
</BoxContainer>
</PanelContainer>
</BoxContainer>
</Control>
<!-- Bottom window for tips -->
<PanelContainer Name="LoginTips" StyleClasses="AngleRect" Margin="0 10" MaxWidth="600" VerticalExpand="True" VerticalAlignment="Bottom">
<BoxContainer Orientation="Vertical" VerticalExpand="True">

View file

@ -18,7 +18,7 @@ namespace Content.Client.Launcher
[GenerateTypedNameReferences]
public sealed partial class LauncherConnectingGui : Control
{
private const float RedialWaitTimeSeconds = 15f;
private const float RedialWaitTimeSeconds = 30f; // Sunrise-edit
private readonly LauncherConnecting _state;
private float _waitTime;

View file

@ -9,7 +9,8 @@
xmlns:lobbyUi="clr-namespace:Content.Client.Lobby.UI"
xmlns:info="clr-namespace:Content.Client.Info"
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Chat.Widgets"
xmlns:changelog="clr-namespace:Content.Client.Changelog">
xmlns:changelog="clr-namespace:Content.Client.Changelog"
xmlns:serversHub="clr-namespace:Content.Client._Sunrise.ServersHub">
<!-- Sunrise-start -->
<!-- Background -->
<!--<TextureRect Access="Public" VerticalExpand="True" HorizontalExpand="True" Name="Background"
@ -26,31 +27,43 @@
<!-- LHS Controls -->
<BoxContainer Name="LeftSide" Orientation="Vertical" SeparationOverride="4" HorizontalExpand="True">
<Control Name="DefaultState" VerticalExpand="True">
<BoxContainer Name="TopLeft" Orientation="Vertical">
<BoxContainer Name="TopLeft" Orientation="Horizontal">
<!-- Left Top Panel -->
<PanelContainer StyleClasses="AngleRect" HorizontalAlignment="Left" Name="LeftSideTop"
VerticalAlignment="Top">
<BoxContainer Orientation="Vertical" HorizontalAlignment="Center" MaxWidth="800">
<info:LinkBanner Name="LinkBanner" VerticalExpand="false" HorizontalAlignment="Center"
Margin="3 3 3 3" />
<controls:StripeBack>
<BoxContainer Orientation="Horizontal" SeparationOverride="6" Margin="3 3 3 3">
<cc:UICommandButton Command="observe" Name="ObserveButton" Access="Public"
Text="{Loc 'ui-lobby-observe-button'}"
StyleClasses="ButtonBig"
WindowType="{x:Type lobbyUi:ObserveWarningWindow}" />
<Label Name="StartTime"
Access="Public"
Align="Left"
FontColorOverride="{x:Static maths:Color.DarkGray}"
StyleClasses="LabelBig" HorizontalExpand="True" />
<Button Name="ReadyButton" Access="Public" ToggleMode="True"
Text="{Loc 'ui-lobby-ready-up-button'}"
StyleClasses="ButtonBig" MinWidth="137" />
</BoxContainer>
</controls:StripeBack>
</BoxContainer>
</PanelContainer>
<BoxContainer Orientation="Vertical">
<PanelContainer StyleClasses="AngleRect" HorizontalAlignment="Left" Name="LeftSideTop"
VerticalAlignment="Top" MinWidth="600">
<BoxContainer Orientation="Vertical" HorizontalExpand="True" MaxWidth="800">
<info:LinkBanner Name="LinkBanner" VerticalExpand="false" HorizontalAlignment="Center"
Margin="3 3 3 3" />
<controls:StripeBack>
<BoxContainer Orientation="Horizontal" SeparationOverride="6" Margin="3 3 3 3"
HorizontalExpand="True">
<cc:UICommandButton Command="observe" Name="ObserveButton" Access="Public"
Text="{Loc 'ui-lobby-observe-button'}"
StyleClasses="ButtonBig"
WindowType="{x:Type lobbyUi:ObserveWarningWindow}" />
<Label Name="StartTime"
Access="Public"
Align="Left"
FontColorOverride="{x:Static maths:Color.DarkGray}"
StyleClasses="LabelBig" HorizontalExpand="True" />
<Button Name="ReadyButton" Access="Public" ToggleMode="True"
Text="{Loc 'ui-lobby-ready-up-button'}"
StyleClasses="ButtonBig" MinWidth="137" />
</BoxContainer>
</controls:StripeBack>
</BoxContainer>
</PanelContainer>
<!-- Sunrise-edit: Servers Hub -->
<PanelContainer StyleClasses="AngleRect" HorizontalExpand="True" Margin="5 0 0 0" Name="ServersHub" MinWidth="600" MinHeight="200">
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="3 3 3 3">
<ScrollContainer VerticalExpand="True" HScrollEnabled="False" HorizontalExpand="True">
<serversHub:ServersHub Name="ServersHubBody" Access="Public" />
</ScrollContainer>
</BoxContainer>
</PanelContainer>
<!-- Sunrise-edit: Servers Hub -->
</BoxContainer>
<!-- Voting Popups -->
<BoxContainer Orientation="Vertical" SeparationOverride="4" Name="VoteContainer"
Access="Public" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0 8" />

View file

@ -7,6 +7,7 @@ using Robust.Client.UserInterface.XAML;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using System.Numerics;
using Content.Client._Sunrise.ServersHub;
using Content.Client.Parallax.Managers;
using Content.Client.Resources;
using Robust.Client.Graphics;
@ -60,6 +61,9 @@ namespace Content.Client.Lobby.UI
LocalChangelog.PanelOverride = back;
LobbySongPanel.PanelOverride = back;
ServersHub.PanelOverride = back;
// Sunrise-end
}

View file

@ -5,6 +5,7 @@ using Content.Client.UserInterface.Systems.Ghost.Widgets;
using Content.Shared.Ghost;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Content.Client._Sunrise.ServersHub;
namespace Content.Client.UserInterface.Systems.Ghost;
@ -12,6 +13,7 @@ namespace Content.Client.UserInterface.Systems.Ghost;
public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSystem>
{
[Dependency] private readonly IEntityNetworkManager _net = default!;
[Dependency] private readonly ServersHubManager _serversHubManager = default!;
[UISystemDependency] private readonly GhostSystem? _system = default;
@ -125,6 +127,7 @@ public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSyst
Gui.RequestWarpsPressed += RequestWarps;
Gui.ReturnToBodyPressed += ReturnToBody;
Gui.GhostRolesPressed += GhostRolesPressed;
Gui.ChangeServerPressed += ChangeServerPressed;
Gui.TargetWindow.WarpClicked += OnWarpClicked;
Gui.TargetWindow.OnGhostnadoClicked += OnGhostnadoClicked;
@ -139,6 +142,7 @@ public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSyst
Gui.RequestWarpsPressed -= RequestWarps;
Gui.ReturnToBodyPressed -= ReturnToBody;
Gui.GhostRolesPressed -= GhostRolesPressed;
Gui.ChangeServerPressed -= ChangeServerPressed;
Gui.TargetWindow.WarpClicked -= OnWarpClicked;
Gui.Hide();
@ -160,4 +164,9 @@ public sealed class GhostUIController : UIController, IOnSystemChanged<GhostSyst
{
_system?.OpenGhostRoles();
}
private void ChangeServerPressed()
{
_serversHubManager.OpenServersHub();
}
}

View file

@ -1,9 +1,14 @@
<widgets:GhostGui xmlns="https://spacestation14.io"
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Ghost.Widgets"
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" />
<BoxContainer Orientation="Vertical">
<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" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
<Button Name="ChangeServerButton" Text="{Loc ghost-gui-change-server}"/>
</BoxContainer>
</BoxContainer>
</widgets:GhostGui>

View file

@ -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? ChangeServerPressed;
public GhostGui()
{
@ -26,6 +27,7 @@ public sealed partial class GhostGui : UIWidget
GhostWarpButton.OnPressed += _ => RequestWarpsPressed?.Invoke();
ReturnToBodyButton.OnPressed += _ => ReturnToBodyPressed?.Invoke();
GhostRolesButton.OnPressed += _ => GhostRolesPressed?.Invoke();
ChangeServerButton.OnPressed += _ => ChangeServerPressed?.Invoke();
}
public void Hide()

View file

@ -0,0 +1,12 @@
<Control xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
<BoxContainer Orientation="Vertical">
<controls:StripeBack>
<BoxContainer Orientation="Horizontal">
<Label HorizontalExpand="True" Name="TotalPlayers" VAlign="Center"
StyleClasses="LabelHeading" Align="Center"/>
</BoxContainer>
</controls:StripeBack>
<controls:ListContainer Name="ServersList" Access="Public" HorizontalExpand="True"/>
</BoxContainer>
</Control>

View file

@ -0,0 +1,76 @@
using System.Linq;
using Content.Client.UserInterface.Controls;
using Content.Shared._Sunrise.ServersHub;
using Robust.Client;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._Sunrise.ServersHub
{
[GenerateTypedNameReferences]
public sealed partial class ServersHub : Control
{
[Dependency] private readonly IGameController _gameController = default!;
[Dependency] private readonly ServersHubManager _serversHubManager = default!;
private Action<string>? ConnectServer { get; set; }
public ServersHub()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
_serversHubManager.ServersDataListChanged += RefreshServersList;
ServersList.GenerateItem += GenerateButton;
RefreshServersList(_serversHubManager.ServersDataList);
ConnectServer += ConnectToServer;
}
private void ConnectToServer(string url)
{
// Почему-то данная хуйня всегда будет закрывать окно игры и открывать заново, даже если подключение идет
// к серверу идентичного билда, почему нельзя было сделать метод редиала в этом же окне - загадка.
// Я был рад поебать позги ПЖБ но мне кажется он опять пошлет меня нахуй.
_gameController.Redial(url);
}
private void RefreshServersList(List<ServerHubEntry> servers)
{
var sortedServers = new List<ServerHubEntry>(servers);
sortedServers.Sort(Compare);
ServersList.PopulateList(sortedServers.Select(info => new ServerListData(info))
.ToList());
var totalPlayers = servers.Sum(server => server.CurrentPlayers);
var maxPlayers = servers.Sum(server => server.MaxPlayers);
// Очень круто показывать общее количество игроков со всех серверов, сразу видно какой проект бальшой.
TotalPlayers.Text = $"Сейчас играет: {totalPlayers}/{maxPlayers}";
}
private int Compare(ServerHubEntry x, ServerHubEntry y)
{
return y.CurrentPlayers.CompareTo(x.CurrentPlayers);
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not ServerListData { Info: var info})
return;
var entry = new ServersHubEntry(info.Title, info.CurrentPlayers, info.MaxPlayers, info.ConnectUrl, info.CanConnect, ConnectServer);
button.AddChild(entry);
button.ToolTip = $"Режим: {info.Preset} | Станция: {info.StationName}";
// Уменьшение задержки до милипиздрической ибо зумерки любят так быстро водить мышкой что чаще всего вообще
// не знают что в игре существуют такие подсказки (Проверено на причинах блокировки ролей в лобби, они их
// в лоб не видят)
button.TooltipDelay = 0.1f;
}
}
public record ServerListData(ServerHubEntry Info) : ListData;
}

View file

@ -0,0 +1,29 @@
<PanelContainer xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
Name="BackgroundColorPanel">
<BoxContainer Orientation="Vertical">
<customControls:HSeparator/>
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
SeparationOverride="3">
<customControls:VSeparator/>
<Label Name="TitleLabel"
SizeFlagsStretchRatio="5"
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Label Name="PlayersCountLabel"
SizeFlagsStretchRatio="1"
HorizontalExpand="True"
Align="Right"
ClipText="True"/>
<customControls:VSeparator/>
<Button Name="ConnectButton"
Text="Подключиться"
SizeFlagsStretchRatio="3"
MinWidth="180"/>
<customControls:VSeparator/>
</BoxContainer>
<customControls:HSeparator/>
</BoxContainer>
</PanelContainer>

View file

@ -0,0 +1,28 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._Sunrise.ServersHub;
[GenerateTypedNameReferences]
public sealed partial class ServersHubEntry : PanelContainer
{
private readonly Action<string>? _connectToServer;
public ServersHubEntry(string serverTitle, int currentPlayerCount, int maxPlayerCount, string connectUrl, bool canConnect, Action<string>? connectToServer)
{
_connectToServer = connectToServer;
RobustXamlLoader.Load(this);
TitleLabel.Text = serverTitle;
PlayersCountLabel.Text = $"{currentPlayerCount}/{maxPlayerCount}";
ConnectButton.OnPressed += _ => ConnectToServer(connectUrl);
// Нам нахуй не нужна локализация, поэтому так.
ConnectButton.Text = canConnect ? "Подключиться" : "Вы уже здесь";
// Банальная защита от дебила, ибо реконект к серверу на котором ты есть занимает столько же как и к другому.
ConnectButton.Disabled = !canConnect;
}
private void ConnectToServer(string url) => _connectToServer?.Invoke(url);
}

View file

@ -0,0 +1,38 @@
using Content.Shared._Sunrise.ServersHub;
using Robust.Shared.Network;
namespace Content.Client._Sunrise.ServersHub;
public partial class ServersHubManager
{
[Dependency] private readonly IClientNetManager _netManager = default!;
public event Action<List<ServerHubEntry>>? ServersDataListChanged;
public List<ServerHubEntry> ServersDataList = [];
private ServersHubUi? _menu;
public void Initialize()
{
_netManager.RegisterNetMessage<MsgFullServerHubList>(OnServersDataChanged);
_menu = new ServersHubUi();
}
// Ахуеть какой костыль, но явно лучше чем писать лишние 300 строк.
public void OpenServersHub()
{
_menu = new ServersHubUi();
_menu.OnClose += _menu.Close;
_menu.OpenCentered();
}
private void OnServersDataChanged(MsgFullServerHubList msg)
{
// Храним всю инфу внутри данного менеджера чтобы даже если конект с сервером будет разорван у нас работал хаб,
// и его можно было отобразить после отключения или бана.
ServersDataList = msg.ServersHubEntries;
ServersDataListChanged?.Invoke(ServersDataList);
}
}

View file

@ -0,0 +1,10 @@
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:serversHub="clr-namespace:Content.Client._Sunrise.ServersHub"
Title="Servers Hub"
MinSize="650 300">
<BoxContainer Orientation="Vertical" VerticalExpand="True" HorizontalExpand="True" Margin="3 3 3 3">
<ScrollContainer VerticalExpand="True" HScrollEnabled="False" HorizontalExpand="True" MinWidth="300">
<serversHub:ServersHub Name="ServersHubBody" Access="Public" />
</ScrollContainer>
</BoxContainer>
</DefaultWindow>

View file

@ -0,0 +1,16 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client._Sunrise.ServersHub;
[GenerateTypedNameReferences]
public sealed partial class ServersHubUi : DefaultWindow
{
public ServersHubUi()
{
RobustXamlLoader.Load(this);
// Я не ебу что тут вообще еще должно быть, вся логика один хуй лежит внутри Control
}
}

View file

@ -1,4 +1,5 @@
using Content.Server._Sunrise.GuideGenerator;
using Content.Server._Sunrise.ServersHub;
using Content.Server._Sunrise.TTS;
using Content.Server.Acz;
using Content.Server.Administration;
@ -108,6 +109,8 @@ namespace Content.Server.Entry
IoCManager.Resolve<ServerInfoManager>().Initialize();
IoCManager.Resolve<ServerApi>().Initialize();
IoCManager.Resolve<ServersHubManager>().Initialize(); // Sunrise-Hub
_voteManager.Initialize();
_updateManager.Initialize();
_playTimeTracking.Initialize();

View file

@ -1,3 +1,4 @@
using Content.Server._Sunrise.ServersHub;
using Content.Server._Sunrise.TTS;
using Content.Server.Administration;
using Content.Server.Administration.Logs;
@ -65,6 +66,8 @@ namespace Content.Server.IoC
IoCManager.Register<ISharedPlaytimeManager, PlayTimeTrackingManager>();
IoCManager.Register<ServerApi>();
IoCManager.Register<JobWhitelistManager>();
IoCManager.Register<ServersHubManager>(); // Sunrise-Hub
}
}
}

View file

@ -0,0 +1,168 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Content.Shared._Sunrise.ServersHub;
using Content.Shared._Sunrise.SunriseCCVars;
using JetBrains.Annotations;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Player;
namespace Content.Server._Sunrise.ServersHub;
public sealed partial class ServersHubManager
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IServerNetManager _netMgr = default!;
private readonly List<ServerHubEntry> _serverDataList = new();
private List<string> _serversList = new();
private readonly HttpClient _httpClient = new();
private ISawmill _sawmill = default!;
private CancellationTokenSource _cts = new();
// Не уверен что надо так часто это делать.
private readonly TimeSpan _updateRate = TimeSpan.FromSeconds(15);
private static readonly Regex RestrictedNameRegex = ServerNameRegex();
public void Initialize()
{
_sawmill = _logManager.GetSawmill("serversHub");
_cfg.OnValueChanged(SunriseCCVars.ServersHubList, OnServerListChanged, true);
Task.Run(async () => await PeriodicUpdateServerData(_cts.Token));
_netMgr.RegisterNetMessage<MsgFullServerHubList>();
}
private void OnServerListChanged(string serverList)
{
_serversList = serverList.Split(',').ToList();
}
// Ахуенный план, надеждный блядь как швейцарские часы нахуй.
private async Task PeriodicUpdateServerData(CancellationToken token)
{
try
{
while (!token.IsCancellationRequested)
{
await UpdateServerData();
await Task.Delay(_updateRate, token);
}
}
catch (TaskCanceledException)
{
_sawmill.Info($"Task was cancelled");
}
}
private async Task UpdateServerData()
{
_serverDataList.Clear();
if (_serversList.Count == 0)
return;
foreach (var serverUrl in _serversList)
{
var data = await RefreshServerData(serverUrl);
if (data == null)
continue;
var thisServerUrl = _cfg.GetCVar(CVars.HubServerUrl);
var serverName = RestrictedNameRegex.Replace(data.Name, string.Empty);
_serverDataList.Add(new ServerHubEntry(
serverName,
data.Map ?? "",
data.Preset ?? "",
data.Players,
data.Soft_Max_Players,
$"ss14s://{NormalizeUrl(serverUrl)}",
NormalizeUrl(thisServerUrl) != NormalizeUrl(serverUrl)));
}
SendFullPlayerList(_playerManager.Sessions);
}
private string NormalizeUrl(string url)
{
try
{
var uri = new Uri(url);
return $"{uri.Host}{uri.AbsolutePath.TrimEnd('/')}";
}
catch (UriFormatException)
{
_sawmill.Error($"Invalid URL format: {url}");
return url;
}
}
private async Task<ServerDataResponse?> RefreshServerData(string url, CancellationToken cancel = default)
{
using var resp = await _httpClient.GetAsync($"{url}/status", cancel);
if (resp.StatusCode == HttpStatusCode.NotFound)
return null;
if (!resp.IsSuccessStatusCode)
{
_sawmill.Error("Auth server returned bad response {StatusCode}!", resp.StatusCode);
return null;
}
var responseData = await resp.Content.ReadFromJsonAsync<ServerDataResponse>(cancellationToken: cancel);
return responseData;
}
// Я в душе не ебу почему без _ оно не хочет парсить некоторые строки с json, но кому не похуй?
[UsedImplicitly]
private sealed record ServerDataResponse(
string Name,
string? Map,
int Round_Id,
int Players,
int Soft_Max_Players,
bool Panic_Bunker,
int Run_Level,
string? Preset,
string? Round_Start_Time);
private void SendFullPlayerList(ICommonSession[] sessions)
{
var netMsg = new MsgFullServerHubList();
netMsg.ServersHubEntries = _serverDataList;
foreach (var session in sessions)
{
try
{
_netMgr.ServerSendMessage(netMsg, session.Channel);
}
catch (Exception ex)
{
// Без данного Exception любые ошибки будут происходить абсолютно незаметно потому что вызывается это в асинхронном таске.
_sawmill.Error($"Failed to send event to {session.Name}: {ex}");
}
}
}
[GeneratedRegex(@"[^А-Яа-яA-Za-zёЁ0-9' \-\.\[\]:|]")]
private static partial Regex ServerNameRegex();
}

View file

@ -0,0 +1,45 @@
using Lidgren.Network;
using Robust.Shared.Network;
using Robust.Shared.Serialization;
namespace Content.Shared._Sunrise.ServersHub;
public sealed class MsgFullServerHubList : NetMessage
{
public override MsgGroups MsgGroup => MsgGroups.EntityEvent;
public List<ServerHubEntry> ServersHubEntries { get; set; } = [];
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
var count = buffer.ReadInt32();
ServersHubEntries.EnsureCapacity(count);
for (var i = 0; i < count; i++)
{
ServersHubEntries.Add(new ServerHubEntry(
buffer.ReadString(),
buffer.ReadString(),
buffer.ReadString(),
buffer.ReadInt32(),
buffer.ReadInt32(),
buffer.ReadString(),
buffer.ReadBoolean()
));
}
}
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
{
buffer.Write(ServersHubEntries.Count);
foreach (var server in ServersHubEntries)
{
buffer.Write(server.Title);
buffer.Write(server.StationName);
buffer.Write(server.Preset);
buffer.Write(server.CurrentPlayers);
buffer.Write(server.MaxPlayers);
buffer.Write(server.ConnectUrl);
buffer.Write(server.CanConnect);
}
}
}

View file

@ -0,0 +1,14 @@
using Robust.Shared.Serialization;
namespace Content.Shared._Sunrise.ServersHub
{
[Serializable, NetSerializable]
public sealed record ServerHubEntry(
string Title,
string StationName,
string Preset,
int CurrentPlayers,
int MaxPlayers,
string ConnectUrl,
bool CanConnect);
}

View file

@ -90,4 +90,14 @@ public sealed class SunriseCCVars
/// </summary>
public static readonly CVarDef<bool> PeacefulRoundEnd =
CVarDef.Create("game.peaceful_end", true, CVar.SERVERONLY);
/*
* Servers Hub
*/
/// <summary>
/// Список серверов отображаемых в хабе. Разделяются через запятую.
/// </summary>
public static readonly CVarDef<string> ServersHubList =
CVarDef.Create("servers_hub.urls", "", CVar.SERVERONLY);
}

View file

@ -209,3 +209,16 @@ Entries:
type: Tweak
id: 18
time: '2024-06-04T07:01:10.279625+00:00'
- author: VigersRay
changes:
- message: "\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D \u0445\u0430\u0431\
\ \u0441\u0435\u0440\u0432\u0435\u0440\u043E\u0432 \u043F\u0440\u043E\u0435\u043A\
\u0442\u0430 \u0432 \u043B\u043E\u0431\u0431\u0438, \u0438\u043D\u0442\u0435\
\u0440\u0444\u0435\u0439\u0441 \u043F\u0440\u0438\u0437\u0440\u0430\u043A\u0430\
\ \u0438 \u043E\u043A\u043D\u043E \u0434\u0438\u0441\u043A\u043E\u043D\u0435\
\u043A\u0442\u0430 \u0441 \u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E\u0441\
\u0442\u044C\u044E \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\
\u044F."
type: Add
id: 19
time: '2024-06-04T06:27:00.064518+00:00'

View file

@ -30,3 +30,6 @@ ghost-roles-window-no-roles-available-label = В настоящее время
ghost-roles-window-rules-footer = Кнопка станет доступна через { $time } секунд (эта задержка нужна, чтобы убедиться, что вы прочитали правила).
ghost-return-to-body-title = Вернуться в тело
ghost-return-to-body-text = Вас воскрешают! Вернуться в своё тело?
# Sunrise-edit
ghost-gui-change-server = Другие сервера