[feat] donate

# Conflicts:
#	Content.Client/Entry/EntryPoint.cs
#	Content.Client/IoC/ClientContentIoC.cs
#	Content.Server/Chat/Managers/ChatManager.cs
#	Content.Server/Entry/EntryPoint.cs
#	Content.Server/GameTicking/GameTicker.Player.cs
#	Content.Server/GameTicking/GameTicker.StatusShell.cs
#	Content.Server/IoC/ServerContentIoC.cs
#	Content.Shared/Humanoid/HumanoidCharacterAppearance.cs
#	Content.Shared/Humanoid/Markings/MarkingPrototype.cs
#	Content.Shared/Preferences/HumanoidCharacterProfile.cs
This commit is contained in:
rhailrake
2023-04-25 19:46:20 +06:00
committed by Remuchi
parent 0cbb69d0a1
commit 4e85539ec6
29 changed files with 809 additions and 83 deletions

View File

@@ -21,6 +21,8 @@ using Content.Client.Singularity;
using Content.Client.Stylesheets;
using Content.Client.Viewport;
using Content.Client.Voting;
using Content.Client.White.JoinQueue;
using Content.Client.White.Sponsors;
using Content.Shared.Ame;
using Content.Shared.Gravity;
using Content.Shared.Localizations;
@@ -71,6 +73,11 @@ namespace Content.Client.Entry
[Dependency] private readonly IReplayLoadManager _replayLoad = default!;
[Dependency] private readonly ILogManager _logManager = default!;
//WD-EDIT
[Dependency] private readonly SponsorsManager _sponsorsManager = default!;
[Dependency] private readonly JoinQueueManager _queueManager = default!;
//WD-EDIT
public override void Init()
{
ClientContentIoC.Register();
@@ -163,6 +170,11 @@ namespace Content.Client.Entry
_userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme));
_documentParsingManager.Initialize();
//WD-EDIT
_sponsorsManager.Initialize();
_queueManager.Initialize();
//WD-EDIT
_baseClient.RunLevelChanged += (_, args) =>
{
if (args.NewLevel == ClientRunLevel.Initialize)

View File

@@ -1,4 +1,5 @@
using System.Linq;
using Content.Client.White.Sponsors;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
@@ -19,6 +20,10 @@ public sealed partial class MarkingPicker : Control
[Dependency] private readonly MarkingManager _markingManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
//WD-EDIT
[Dependency] private readonly SponsorsManager _sponsorsManager = default!;
//WD-EDIT
public Action<MarkingSet>? OnMarkingAdded;
public Action<MarkingSet>? OnMarkingRemoved;
public Action<MarkingSet>? OnMarkingColorChange;
@@ -202,6 +207,18 @@ public sealed partial class MarkingPicker : Control
var item = CMarkingsUnused.AddItem($"{GetMarkingName(marking)}", marking.Sprites[0].Frame0());
item.Metadata = marking;
//WD-EDIT
if (marking.SponsorOnly)
{
item.Disabled = true;
if (_sponsorsManager.TryGetInfo(out var sponsor))
{
item.Disabled = !sponsor.AllowedMarkings.Contains(marking.ID);
}
}
//WD-EDIT
}
CMarkingPoints.Visible = _currentMarkings.PointsLeft(_selectedMarkingCategory) != -1;

View File

@@ -1,4 +1,5 @@
using System.Linq;
using Content.Client.White.Sponsors;
using Content.Shared.Humanoid.Markings;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
@@ -12,6 +13,10 @@ public sealed partial class SingleMarkingPicker : BoxContainer
{
[Dependency] private readonly MarkingManager _markingManager = default!;
//WD-EDIT
[Dependency] private readonly SponsorsManager _sponsorsManager = default!;
//WD-EDIT
/// <summary>
/// What happens if a marking is selected.
/// It will send the 'slot' (marking index)
@@ -191,6 +196,17 @@ public sealed partial class SingleMarkingPicker : BoxContainer
var item = MarkingList.AddItem(Loc.GetString($"marking-{id}"), marking.Sprites[0].Frame0());
item.Metadata = marking.ID;
//WD-EDIT
if (marking.SponsorOnly)
{
item.Disabled = true;
if (_sponsorsManager.TryGetInfo(out var sponsor))
{
item.Disabled = !sponsor.AllowedMarkings.Contains(marking.ID);
}
}
//WD-EDIT
if (_markings[Slot].MarkingId == id)
{
_ignoreItemSelected = true;

View File

@@ -2,7 +2,6 @@ using Content.Client.Administration.Managers;
using Content.Client.Changelog;
using Content.Client.Chat.Managers;
using Content.Client.Clickable;
using Content.Client.Options;
using Content.Client.Eui;
using Content.Client.GhostKick;
using Content.Client.Info;
@@ -15,14 +14,13 @@ using Content.Client.Fullscreen;
using Content.Client.Stylesheets;
using Content.Client.Viewport;
using Content.Client.Voting;
using Content.Shared.Administration;
using Content.Shared.Administration.Logs;
using Content.Shared.Module;
using Content.Client.Guidebook;
using Content.Client.Replay;
using Content.Client.White.JoinQueue;
using Content.Client.White.Sponsors;
using Content.Shared.Administration.Managers;
namespace Content.Client.IoC
{
internal static class ClientContentIoC
@@ -49,6 +47,11 @@ namespace Content.Client.IoC
IoCManager.Register<JobRequirementsManager>();
IoCManager.Register<DocumentParsingManager>();
IoCManager.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
//WD-EDIT
IoCManager.Register<JoinQueueManager>();
IoCManager.Register<SponsorsManager>();
//WD-EDIT
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Content.Client.White.Sponsors;
using Content.Shared.Preferences;
using Robust.Client;
using Robust.Shared.IoC;
@@ -19,6 +20,10 @@ namespace Content.Client.Preferences
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IBaseClient _baseClient = default!;
//WD-EDIT
[Dependency] private readonly SponsorsManager _sponsorsManager = default!;
//WD-EDIT
public event Action? OnServerDataLoaded;
public GameSettings Settings { get; private set; } = default!;
@@ -60,7 +65,10 @@ namespace Content.Client.Preferences
public void UpdateCharacter(ICharacterProfile profile, int slot)
{
profile.EnsureValid();
//WD-EDIT
var allowedMarkings = _sponsorsManager.TryGetInfo(out var sponsor) ? sponsor.AllowedMarkings : new string[]{};
profile.EnsureValid(allowedMarkings);
//WD-EDIT
var characters = new Dictionary<int, ICharacterProfile>(Preferences.Characters) {[slot] = profile};
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor);
var msg = new MsgUpdateCharacter

View File

@@ -0,0 +1,26 @@
using Content.Shared.White.JoinQueue;
using Robust.Client.State;
using Robust.Shared.Network;
namespace Content.Client.White.JoinQueue;
public sealed class JoinQueueManager
{
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
public void Initialize()
{
_netManager.RegisterNetMessage<MsgQueueUpdate>(OnQueueUpdate);
}
private void OnQueueUpdate(MsgQueueUpdate msg)
{
if (_stateManager.CurrentState is not QueueState)
{
_stateManager.RequestStateChange<QueueState>();
}
((QueueState) _stateManager.CurrentState).OnQueueUpdate(msg);
}
}

View File

@@ -0,0 +1,34 @@
<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 Orientation="Horizontal" VerticalAlignment="Bottom" Margin="0 20 0 0">
<Button Name="PriorityJoinButton" Text="{Loc 'queue-priority-join'}" HorizontalExpand="True" StyleClasses="OpenRight" />
</BoxContainer>
</BoxContainer>
</Control>
</Control>

View File

@@ -0,0 +1,36 @@
using Content.Shared.CCVar;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
namespace Content.Client.White.JoinQueue;
[GenerateTypedNameReferences]
public sealed partial class QueueGui : Control
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
public event Action? QuitPressed;
public QueueGui()
{
RobustXamlLoader.Load(this);
LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide);
QuitButton.OnPressed += (_) => QuitPressed?.Invoke();
PriorityJoinButton.OnPressed += (_) =>
{
var linkPatreon = _cfg.GetCVar(CCVars.InfoLinksPatreon);
IoCManager.Resolve<IUriOpener>().OpenUri(linkPatreon);
};
}
public void UpdateInfo(int total, int position)
{
QueueTotal.Text = total.ToString();
QueuePosition.Text = position.ToString();
}
}

View File

@@ -0,0 +1,53 @@
using Robust.Client.Console;
using Robust.Client.GameObjects;
using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Shared.Player;
using Content.Shared.White.JoinQueue;
using Robust.Client.Audio;
namespace Content.Client.White.JoinQueue;
public sealed class QueueState : State
{
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
private const string JoinSoundPath = "/Audio/Effects/voteding.ogg";
private QueueGui? _gui;
protected override void Startup()
{
_gui = new QueueGui();
_userInterfaceManager.StateRoot.AddChild(_gui);
_gui.QuitPressed += OnQuitPressed;
}
protected override void Shutdown()
{
_gui!.QuitPressed -= OnQuitPressed;
_gui.Dispose();
Ding();
}
private void Ding()
{
if (IoCManager.Resolve<IEntityManager>().TrySystem<AudioSystem>(out var audio))
{
audio.PlayGlobal(JoinSoundPath, Filter.Local(), false);
}
}
public void OnQueueUpdate(MsgQueueUpdate msg)
{
_gui?.UpdateInfo(msg.Total, msg.Position);
}
private void OnQuitPressed()
{
_consoleHost.ExecuteCommand("quit");
}
}

View File

@@ -0,0 +1,23 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.White.Sponsors;
using Robust.Shared.Network;
namespace Content.Client.White.Sponsors;
public sealed class SponsorsManager
{
[Dependency] private readonly IClientNetManager _netMgr = default!;
private SponsorInfo? _info;
public void Initialize()
{
_netMgr.RegisterNetMessage<MsgSponsorInfo>(msg => _info = msg.Info);
}
public bool TryGetInfo([NotNullWhen(true)] out SponsorInfo? sponsor)
{
sponsor = _info;
return _info != null;
}
}

View File

@@ -6,6 +6,7 @@ using Content.Server.Administration.Managers;
using Content.Server.Administration.Systems;
using Content.Server.MoMMI;
using Content.Server.Preferences.Managers;
using Content.Server.White.Sponsors;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Chat;
@@ -46,6 +47,10 @@ namespace Content.Server.Chat.Managers
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
/// WD-EDIT
[Dependency] private readonly SponsorsManager _sponsorsManager = default!;
/// WD-EDIT
/// <summary>
/// The maximum length a player-sent message can be sent
/// </summary>
@@ -69,7 +74,8 @@ namespace Content.Server.Chat.Managers
private void OnOocEnabledChanged(bool val)
{
if (_oocEnabled == val) return;
if (_oocEnabled == val)
return;
_oocEnabled = val;
DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-ooc-chat-enabled-message" : "chat-manager-ooc-chat-disabled-message"));
@@ -77,7 +83,8 @@ namespace Content.Server.Chat.Managers
private void OnAdminOocEnabledChanged(bool val)
{
if (_adminOocEnabled == val) return;
if (_adminOocEnabled == val)
return;
_adminOocEnabled = val;
DispatchServerAnnouncement(Loc.GetString(val ? "chat-manager-admin-ooc-chat-enabled-message" : "chat-manager-admin-ooc-chat-disabled-message"));
@@ -119,7 +126,7 @@ namespace Content.Server.Chat.Managers
public void DispatchServerMessage(ICommonSession player, string message, bool suppressLog = false)
{
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", FormattedMessage.EscapeText(message)));
ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, default, false, player.ConnectedClient);
ChatMessageToOne(ChatChannel.Server, message, wrappedMessage, default, false, player.Channel);
if (!suppressLog)
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Server message to {player:Player}: {message}");
@@ -127,7 +134,7 @@ namespace Content.Server.Chat.Managers
public void SendAdminAnnouncement(string message)
{
var clients = _adminManager.ActiveAdmins.Select(p => p.ConnectedClient);
var clients = _adminManager.ActiveAdmins.Select(p => p.Channel);
var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message",
("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), ("message", FormattedMessage.EscapeText(message)));
@@ -138,7 +145,7 @@ namespace Content.Server.Chat.Managers
public void SendAdminAlert(string message)
{
var clients = _adminManager.ActiveAdmins.Select(p => p.ConnectedClient);
var clients = _adminManager.ActiveAdmins.Select(p => p.Channel);
var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message",
("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), ("message", FormattedMessage.EscapeText(message)));
@@ -149,7 +156,7 @@ namespace Content.Server.Chat.Managers
public void SendAdminAlert(EntityUid player, string message)
{
var mindSystem = _entityManager.System<SharedMindSystem>();
if (!mindSystem.TryGetMind(player, out var mindId, out var mind))
if (!mindSystem.TryGetMind(player, out _, out var mind))
{
SendAdminAlert(message);
return;
@@ -230,12 +237,19 @@ namespace Content.Server.Chat.Managers
var prefs = _preferencesManager.GetPreferences(player.UserId);
colorOverride = prefs.AdminOOCColor;
}
if (player.ConnectedClient.UserData.PatronTier is { } patron &&
if (player.Channel.UserData.PatronTier is { } patron &&
PatronOocColors.TryGetValue(patron, out var patronColor))
{
wrappedMessage = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", patronColor),("playerName", player.Name), ("message", FormattedMessage.EscapeText(message)));
}
//WD-EDIT
if (_sponsorsManager.TryGetInfo(player.UserId, out var sponsorData) && sponsorData.OOCColor != null)
{
wrappedMessage = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", sponsorData.OOCColor),("playerName", player.Name), ("message", FormattedMessage.EscapeText(message)));
}
//WD-EDIT
//TODO: player.Name color, this will need to change the structure of the MsgChatMessage
ChatMessageToAll(ChatChannel.OOC, message, wrappedMessage, EntityUid.Invalid, hideChat: false, recordReplay: true, colorOverride: colorOverride, author: player.UserId);
_mommiLink.SendOOCMessage(player.Name, message);
@@ -250,14 +264,14 @@ namespace Content.Server.Chat.Managers
return;
}
var clients = _adminManager.ActiveAdmins.Select(p => p.ConnectedClient);
var clients = _adminManager.ActiveAdmins.Select(p => p.Channel);
var wrappedMessage = Loc.GetString("chat-manager-send-admin-chat-wrap-message",
("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")),
("playerName", player.Name), ("message", FormattedMessage.EscapeText(message)));
foreach (var client in clients)
{
var isSource = client != player.ConnectedClient;
var isSource = client != player.Channel;
ChatMessageToOne(ChatChannel.AdminChat,
message,
wrappedMessage,
@@ -283,7 +297,7 @@ namespace Content.Server.Chat.Managers
user?.AddEntity(netSource);
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
_netManager.ServerSendMessage(new MsgChatMessage() { Message = msg }, client);
_netManager.ServerSendMessage(new MsgChatMessage { Message = msg }, client);
if (!recordReplay)
return;
@@ -296,7 +310,10 @@ namespace Content.Server.Chat.Managers
}
public void ChatMessageToMany(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, IEnumerable<INetChannel> clients, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, NetUserId? author = null)
=> ChatMessageToMany(channel, message, wrappedMessage, source, hideChat, recordReplay, clients.ToList(), colorOverride, audioPath, audioVolume, author);
{
ChatMessageToMany(channel, message, wrappedMessage, source, hideChat, recordReplay, clients.ToList(),
colorOverride, audioPath, audioVolume, author);
}
public void ChatMessageToMany(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, bool recordReplay, List<INetChannel> clients, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0, NetUserId? author = null)
{
@@ -305,7 +322,7 @@ namespace Content.Server.Chat.Managers
user?.AddEntity(netSource);
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
_netManager.ServerSendToMany(new MsgChatMessage() { Message = msg }, clients);
_netManager.ServerSendToMany(new MsgChatMessage { Message = msg }, clients);
if (!recordReplay)
return;
@@ -317,8 +334,17 @@ namespace Content.Server.Chat.Managers
}
}
public void ChatMessageToManyFiltered(Filter filter, ChatChannel channel, string message, string wrappedMessage, EntityUid source,
bool hideChat, bool recordReplay, Color? colorOverride = null, string? audioPath = null, float audioVolume = 0)
public void ChatMessageToManyFiltered(
Filter filter,
ChatChannel channel,
string message,
string wrappedMessage,
EntityUid source,
bool hideChat,
bool recordReplay,
Color? colorOverride = null,
string? audioPath = null,
float audioVolume = 0)
{
if (!recordReplay && !filter.Recipients.Any())
return;
@@ -326,7 +352,7 @@ namespace Content.Server.Chat.Managers
var clients = new List<INetChannel>();
foreach (var recipient in filter.Recipients)
{
clients.Add(recipient.ConnectedClient);
clients.Add(recipient.Channel);
}
ChatMessageToMany(channel, message, wrappedMessage, source, hideChat, recordReplay, clients, colorOverride, audioPath, audioVolume);
@@ -339,7 +365,7 @@ namespace Content.Server.Chat.Managers
user?.AddEntity(netSource);
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
_netManager.ServerSendToAll(new MsgChatMessage() { Message = msg });
_netManager.ServerSendToAll(new MsgChatMessage { Message = msg });
if (!recordReplay)
return;

View File

@@ -3,9 +3,11 @@ using System.Threading.Tasks;
using Content.Server.Database;
using Content.Server.GameTicking;
using Content.Server.Preferences.Managers;
using Content.Server.White.Sponsors;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.White;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
@@ -16,6 +18,7 @@ namespace Content.Server.Connection
public interface IConnectionManager
{
void Initialize();
Task<bool> HavePrivilegedJoin(NetUserId userId); // WD-EDIT
}
/// <summary>
@@ -31,6 +34,10 @@ namespace Content.Server.Connection
[Dependency] private readonly ILocalizationManager _loc = default!;
[Dependency] private readonly ServerDbEntryManager _serverDbEntry = default!;
//WD-EDIT
[Dependency] private readonly SponsorsManager _sponsorsManager = default!;
//WD-EDIT
public void Initialize()
{
_netMgr.Connecting += NetMgrOnConnecting;
@@ -152,13 +159,14 @@ namespace Content.Server.Connection
}
}
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)
//WD-EDIT
var isQueueEnabled = _cfg.GetCVar(WhiteCVars.QueueEnabled);
var isPrivileged = await HavePrivilegedJoin(e.UserId);
if (_plyMgr.PlayerCount >= _cfg.GetCVar(CCVars.SoftMaxPlayers) && !isPrivileged && !isQueueEnabled)
{
return (ConnectionDenyReason.Full, Loc.GetString("soft-player-cap-full"), null);
}
//WD-EDIT
var bans = await _db.GetServerBansAsync(addr, userId, hwId, includeUnbanned: false);
if (bans.Count > 0)
@@ -205,5 +213,20 @@ namespace Content.Server.Connection
await _db.AssignUserIdAsync(name, assigned);
return assigned;
}
//WD-EDIT
public async Task<bool> HavePrivilegedJoin(NetUserId userId)
{
var adminData = await _dbManager.GetAdminDataForAsync(userId);
var havePriorityJoin = _sponsorsManager.TryGetInfo(userId, out var sponsorData) && sponsorData.HavePriorityJoin;
var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
status == PlayerGameStatus.JoinedGame;
return adminData != null ||
havePriorityJoin ||
wasInGame;
}
//WD-EDIT
}
}

View File

@@ -29,6 +29,8 @@ using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Server.White.JoinQueue;
using Content.Server.White.Sponsors;
namespace Content.Server.Entry
{
@@ -41,7 +43,6 @@ namespace Content.Server.Entry
private IVoteManager _voteManager = default!;
private ServerUpdateManager _updateManager = default!;
private PlayTimeTrackingManager? _playTimeTracking;
private IEntitySystemManager? _sysMan;
private IServerDbManager? _dbManager;
/// <inheritdoc />
@@ -82,31 +83,36 @@ namespace Content.Server.Entry
var configManager = IoCManager.Resolve<IConfigurationManager>();
var dest = configManager.GetCVar(CCVars.DestinationFile);
IoCManager.Resolve<ContentLocalizationManager>().Initialize();
if (string.IsNullOrEmpty(dest)) //hacky but it keeps load times for the generator down.
{
_euiManager = IoCManager.Resolve<EuiManager>();
_voteManager = IoCManager.Resolve<IVoteManager>();
_updateManager = IoCManager.Resolve<ServerUpdateManager>();
_playTimeTracking = IoCManager.Resolve<PlayTimeTrackingManager>();
_sysMan = IoCManager.Resolve<IEntitySystemManager>();
_dbManager = IoCManager.Resolve<IServerDbManager>();
if (!string.IsNullOrEmpty(dest)) //hacky but it keeps load times for the generator down.
return;
logManager.GetSawmill("Storage").Level = LogLevel.Info;
logManager.GetSawmill("db.ef").Level = LogLevel.Info;
_euiManager = IoCManager.Resolve<EuiManager>();
_voteManager = IoCManager.Resolve<IVoteManager>();
_updateManager = IoCManager.Resolve<ServerUpdateManager>();
_playTimeTracking = IoCManager.Resolve<PlayTimeTrackingManager>();
IoCManager.Resolve<IEntitySystemManager>();
_dbManager = IoCManager.Resolve<IServerDbManager>();
IoCManager.Resolve<IAdminLogManager>().Initialize();
IoCManager.Resolve<IConnectionManager>().Initialize();
_dbManager.Init();
IoCManager.Resolve<IServerPreferencesManager>().Init();
IoCManager.Resolve<INodeGroupFactory>().Initialize();
IoCManager.Resolve<ContentNetworkResourceManager>().Initialize();
IoCManager.Resolve<GhostKickManager>().Initialize();
IoCManager.Resolve<ServerInfoManager>().Initialize();
logManager.GetSawmill("Storage").Level = LogLevel.Info;
logManager.GetSawmill("db.ef").Level = LogLevel.Info;
_voteManager.Initialize();
_updateManager.Initialize();
_playTimeTracking.Initialize();
}
IoCManager.Resolve<IAdminLogManager>().Initialize();
IoCManager.Resolve<IConnectionManager>().Initialize();
_dbManager.Init();
IoCManager.Resolve<IServerPreferencesManager>().Init();
IoCManager.Resolve<INodeGroupFactory>().Initialize();
IoCManager.Resolve<ContentNetworkResourceManager>().Initialize();
IoCManager.Resolve<GhostKickManager>().Initialize();
IoCManager.Resolve<ServerInfoManager>().Initialize();
//WD-EDIT
IoCManager.Resolve<SponsorsManager>().Initialize();
IoCManager.Resolve<JoinQueueManager>().Initialize();
//WD-EDIT
_voteManager.Initialize();
_updateManager.Initialize();
_playTimeTracking.Initialize();
}
public override void PostInit()

View File

@@ -7,7 +7,6 @@ using JetBrains.Annotations;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.GameTicking
@@ -32,7 +31,7 @@ namespace Content.Server.GameTicking
if (args.NewStatus != SessionStatus.Disconnected)
{
mind.Session = session;
_pvsOverride.AddSessionOverride(GetNetEntity(mindId.Value), session);
_pvsOverride.AddSessionOverride(mindId.Value, session);
}
DebugTools.Assert(mind.Session == session);
@@ -56,7 +55,7 @@ namespace Content.Server.GameTicking
// Make the player actually join the game.
// timer time must be > tick length
Timer.Spawn(0, () => _playerManager.JoinGame(args.Session));
// Timer.Spawn(0, args.Session.JoinGame); // Moved to `JoinQueueManager`(WD-EDIT)
var record = await _dbManager.GetPlayerRecordByUserId(args.Session.UserId);
var firstConnection = record != null &&
@@ -124,7 +123,8 @@ namespace Content.Server.GameTicking
mind.Session = null;
}
_userDb.ClientDisconnected(session);
if (_playerGameStatuses.ContainsKey(args.Session.UserId)) //WD-EDIT
_userDb.ClientDisconnected(session);
break;
}
}
@@ -166,7 +166,7 @@ namespace Content.Server.GameTicking
_playerGameStatuses[session.UserId] = PlayerGameStatus.JoinedGame;
_db.AddRoundPlayers(RoundId, session.UserId);
RaiseNetworkEvent(new TickerJoinGameEvent(), session.ConnectedClient);
RaiseNetworkEvent(new TickerJoinGameEvent(), session.Channel);
}
private void PlayerJoinLobby(ICommonSession session)
@@ -174,7 +174,7 @@ namespace Content.Server.GameTicking
_playerGameStatuses[session.UserId] = LobbyEnabled ? PlayerGameStatus.NotReadyToPlay : PlayerGameStatus.ReadyToPlay;
_db.AddRoundPlayers(RoundId, session.UserId);
var client = session.ConnectedClient;
var client = session.Channel;
RaiseNetworkEvent(new TickerJoinLobbyEvent(), client);
RaiseNetworkEvent(GetStatusMsg(session), client);
RaiseNetworkEvent(GetInfoMsg(), client);

View File

@@ -1,4 +1,5 @@
using System.Text.Json.Nodes;
using Content.Server.White.JoinQueue;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Robust.Server.ServerStatus;
@@ -28,6 +29,10 @@ namespace Content.Server.GameTicking
/// </summary>
[Dependency] private readonly SharedGameTicker _gameTicker = default!;
// WD-EDIT
[Dependency] private readonly JoinQueueManager _queueManager = default!;
// WD-EDIT
private void InitializeStatusShell()
{
IoCManager.Resolve<IStatusHost>().OnStatusRequest += GetStatusResponse;
@@ -43,7 +48,7 @@ namespace Content.Server.GameTicking
jObject["name"] = _baseServer.ServerName;
jObject["map"] = _gameMapManager.GetSelectedMap()?.MapName;
jObject["round_id"] = _gameTicker.RoundId;
jObject["players"] = _playerManager.PlayerCount;
jObject["players"] = _queueManager.ActualPlayersCount; //WD-EDIT
jObject["soft_max_players"] = _cfg.GetCVar(CCVars.SoftMaxPlayers);
jObject["panic_bunker"] = _cfg.GetCVar(CCVars.PanicBunkerEnabled);
jObject["run_level"] = (int) _runLevel;

View File

@@ -19,6 +19,8 @@ using Content.Server.ServerInfo;
using Content.Server.ServerUpdates;
using Content.Server.Voting.Managers;
using Content.Server.Worldgen.Tools;
using Content.Server.White.JoinQueue;
using Content.Server.White.Sponsors;
using Content.Shared.Administration.Logs;
using Content.Shared.Administration.Managers;
using Content.Shared.Kitchen;
@@ -58,6 +60,11 @@ namespace Content.Server.IoC
IoCManager.Register<PoissonDiskSampler>();
IoCManager.Register<DiscordWebhook>();
IoCManager.Register<ServerDbEntryManager>();
// WD-EDIT
IoCManager.Register<SponsorsManager>();
IoCManager.Register<JoinQueueManager>();
// WD-EDIT
}
}
}

View File

@@ -4,6 +4,7 @@ using System.Threading;
using System.Threading.Tasks;
using Content.Server.Database;
using Content.Server.Humanoid;
using Content.Server.White.Sponsors;
using Content.Shared.CCVar;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
@@ -27,6 +28,10 @@ namespace Content.Server.Preferences.Managers
[Dependency] private readonly IServerDbManager _db = default!;
[Dependency] private readonly IPrototypeManager _protos = default!;
// WD-EDIT
[Dependency] private readonly SponsorsManager _sponsors = default!;
// WD-EDIT
// Cache player prefs on the server so we don't need as much async hell related to them.
private readonly Dictionary<NetUserId, PlayerPrefData> _cachedPlayerPrefs =
new();
@@ -99,7 +104,10 @@ namespace Content.Server.Preferences.Managers
var curPrefs = prefsData.Prefs!;
profile.EnsureValid();
// WD-EDIT
var allowedMarkings = _sponsors.TryGetInfo(message.MsgChannel.UserId, out var sponsor) ? sponsor.AllowedMarkings : new string[]{};
profile.EnsureValid(allowedMarkings);
// WD-EDIT
var profiles = new Dictionary<int, ICharacterProfile>(curPrefs.Characters)
{
@@ -193,6 +201,15 @@ namespace Content.Server.Preferences.Managers
async Task LoadPrefs()
{
var prefs = await GetOrCreatePreferencesAsync(session.UserId);
// WD-EDIT
foreach (var (_, profile) in prefs.Characters)
{
var allowedMarkings = _sponsors.TryGetInfo(session.UserId, out var sponsor) ? sponsor.AllowedMarkings : new string[]{};
profile.EnsureValid(allowedMarkings);
}
// WD-EDIT
prefsData.Prefs = prefs;
prefsData.PrefsLoaded = true;

View File

@@ -0,0 +1,132 @@
using System.Linq;
using Content.Server.Connection;
using Content.Shared.CCVar;
using Content.Shared.White;
using Content.Shared.White.JoinQueue;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Content.Server.White.JoinQueue;
/// <summary>
/// Manages new player connections when the server is full and queues them up, granting access when a slot becomes free
/// </summary>
public sealed class JoinQueueManager
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IConnectionManager _connectionManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IServerNetManager _netManager = default!;
/// <summary>
/// Queue of active player sessions
/// </summary>
private readonly List<ICommonSession> _queue = new(); // Real Queue class can't delete disconnected users
private bool _isEnabled;
public int PlayerInQueueCount => _queue.Count;
public int ActualPlayersCount => _playerManager.PlayerCount - PlayerInQueueCount; // Now it's only real value with actual players count that in game
public void Initialize()
{
_netManager.RegisterNetMessage<MsgQueueUpdate>();
_cfg.OnValueChanged(WhiteCVars.QueueEnabled, OnQueueCVarChanged, true);
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
}
private void OnQueueCVarChanged(bool value)
{
_isEnabled = value;
if (value)
return;
foreach (var session in _queue)
{
session.Channel.Disconnect("Queue was disabled");
}
}
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
switch (e.NewStatus)
{
case SessionStatus.Connected:
{
if (!_isEnabled)
{
SendToGame(e.Session);
return;
}
var isPrivileged = await _connectionManager.HavePrivilegedJoin(e.Session.UserId);
var haveFreeSlot = _playerManager.PlayerCount < _cfg.GetCVar(CCVars.SoftMaxPlayers);
if (isPrivileged || haveFreeSlot)
{
SendToGame(e.Session);
return;
}
_queue.Add(e.Session);
ProcessQueue(false);
break;
}
case SessionStatus.Disconnected:
{
_queue.Remove(e.Session);
ProcessQueue(true);
break;
}
}
}
/// <summary>
/// If possible, takes the first player in the queue and sends him into the game
/// </summary>
private void ProcessQueue(bool isDisconnect)
{
var players = ActualPlayersCount;
if (isDisconnect)
players--; // Decrease currently disconnected session but that has not yet been deleted
var haveFreeSlot = players < _cfg.GetCVar(CCVars.SoftMaxPlayers);
var queueContains = _queue.Count > 0;
if ((!_isEnabled || haveFreeSlot) && queueContains)
{
var session = _queue.First();
_queue.Remove(session);
SendToGame(session);
}
SendUpdateMessages();
}
/// <summary>
/// Sends messages to all players in the queue with the current state of the queue
/// </summary>
private void SendUpdateMessages()
{
for (var i = 0; i < _queue.Count; i++)
{
_queue[i].Channel.SendMessage(new MsgQueueUpdate
{
Total = _queue.Count,
Position = i + 1,
});
}
}
/// <summary>
/// Letting player's session into game, change player state
/// </summary>
private void SendToGame(ICommonSession s)
{
Timer.Spawn(0, () => _playerManager.JoinGame(s));
}
}

View File

@@ -0,0 +1,92 @@
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Content.Shared.CCVar;
using Content.Shared.White;
using Content.Shared.White.Sponsors;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Utility;
namespace Content.Server.White.Sponsors;
public sealed class SponsorsManager
{
[Dependency] private readonly IServerNetManager _netMgr = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly HttpClient _httpClient = new();
private ISawmill _sawmill = default!;
private string _apiUrl = string.Empty;
private readonly Dictionary<NetUserId, SponsorInfo> _cachedSponsors = new();
public void Initialize()
{
_sawmill = Logger.GetSawmill("sponsors");
_cfg.OnValueChanged(WhiteCVars.SponsorsApiUrl, s => _apiUrl = s, true);
_netMgr.RegisterNetMessage<MsgSponsorInfo>();
_netMgr.Connecting += OnConnecting;
_netMgr.Connected += OnConnected;
_netMgr.Disconnect += OnDisconnect;
}
public bool TryGetInfo(NetUserId userId, [NotNullWhen(true)] out SponsorInfo? sponsor)
{
return _cachedSponsors.TryGetValue(userId, out sponsor);
}
private async Task OnConnecting(NetConnectingArgs e)
{
var info = await LoadSponsorInfo(e.UserId);
if (info?.Tier == null)
{
_cachedSponsors.Remove(e.UserId); // Remove from cache if sponsor expired
return;
}
DebugTools.Assert(!_cachedSponsors.ContainsKey(e.UserId), "Cached data was found on client connect");
_cachedSponsors[e.UserId] = info;
}
private void OnConnected(object? sender, NetChannelArgs e)
{
var info = _cachedSponsors.TryGetValue(e.Channel.UserId, out var sponsor) ? sponsor : null;
var msg = new MsgSponsorInfo() { Info = info };
_netMgr.ServerSendMessage(msg, e.Channel);
}
private void OnDisconnect(object? sender, NetDisconnectedArgs e)
{
_cachedSponsors.Remove(e.Channel.UserId);
}
private async Task<SponsorInfo?> LoadSponsorInfo(NetUserId userId)
{
if (string.IsNullOrEmpty(_apiUrl))
return null;
var url = $"{_apiUrl}/sponsors/{userId.ToString()}";
var response = await _httpClient.GetAsync(url);
if (response.StatusCode == HttpStatusCode.NotFound)
return null;
if (response.StatusCode != HttpStatusCode.OK)
{
var errorText = await response.Content.ReadAsStringAsync();
_sawmill.Error(
"Failed to get player sponsor OOC color from API: [{StatusCode}] {Response}",
response.StatusCode,
errorText);
return null;
}
return await response.Content.ReadFromJsonAsync<SponsorInfo>();
}
}

View File

@@ -188,7 +188,7 @@ namespace Content.Shared.Humanoid
return new(color.RByte, color.GByte, color.BByte);
}
public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearance appearance, string species, Sex sex)
public static HumanoidCharacterAppearance EnsureValid(HumanoidCharacterAppearance appearance, string species, string[] sponsorMarkings) //WD-EDIT
{
var hairStyleId = appearance.HairStyleId;
var facialHairStyleId = appearance.FacialHairStyleId;
@@ -205,27 +205,57 @@ namespace Content.Shared.Humanoid
hairStyleId = HairStyles.DefaultHairStyle;
}
// WD-EDIT
if (proto.TryIndex(hairStyleId, out MarkingPrototype? hairProto) &&
hairProto.SponsorOnly &&
!sponsorMarkings.Contains(hairStyleId))
{
hairStyleId = HairStyles.DefaultHairStyle;
}
// WD-EDIT
if (!markingManager.MarkingsByCategory(MarkingCategories.FacialHair).ContainsKey(facialHairStyleId))
{
facialHairStyleId = HairStyles.DefaultFacialHairStyle;
}
// WD-EDIT
if (proto.TryIndex(facialHairStyleId, out MarkingPrototype? facialHairProto) &&
facialHairProto.SponsorOnly &&
!sponsorMarkings.Contains(facialHairStyleId))
{
facialHairStyleId = HairStyles.DefaultFacialHairStyle;
}
// WD-EDIT
var markingSet = new MarkingSet();
var skinColor = appearance.SkinColor;
if (proto.TryIndex(species, out SpeciesPrototype? speciesProto))
if (!proto.TryIndex(species, out SpeciesPrototype? speciesProto))
{
markingSet = new MarkingSet(appearance.Markings, speciesProto.MarkingPoints, markingManager, proto);
markingSet.EnsureValid(markingManager);
if (!Humanoid.SkinColor.VerifySkinColor(speciesProto.SkinColoration, skinColor))
{
skinColor = Humanoid.SkinColor.ValidSkinTone(speciesProto.SkinColoration, skinColor);
}
markingSet.EnsureSpecies(species, skinColor, markingManager);
markingSet.EnsureSexes(sex, markingManager);
return new HumanoidCharacterAppearance(
hairStyleId,
hairColor,
facialHairStyleId,
facialHairColor,
eyeColor,
skinColor,
markingSet.GetForwardEnumerator().ToList());
}
markingSet = new MarkingSet(appearance.Markings, speciesProto.MarkingPoints, markingManager, proto);
markingSet.EnsureValid(markingManager);
if (!Humanoid.SkinColor.VerifySkinColor(speciesProto.SkinColoration, skinColor))
{
skinColor = Humanoid.SkinColor.ValidSkinTone(speciesProto.SkinColoration, skinColor);
}
markingSet.EnsureSpecies(species, skinColor, markingManager);
// WD-EDIT
markingSet.FilterSponsor(sponsorMarkings, markingManager);
// WD-EDIT
return new HumanoidCharacterAppearance(
hairStyleId,
hairColor,
@@ -238,15 +268,21 @@ namespace Content.Shared.Humanoid
public bool MemberwiseEquals(ICharacterAppearance maybeOther)
{
if (maybeOther is not HumanoidCharacterAppearance other) return false;
if (HairStyleId != other.HairStyleId) return false;
if (!HairColor.Equals(other.HairColor)) return false;
if (FacialHairStyleId != other.FacialHairStyleId) return false;
if (!FacialHairColor.Equals(other.FacialHairColor)) return false;
if (!EyeColor.Equals(other.EyeColor)) return false;
if (!SkinColor.Equals(other.SkinColor)) return false;
if (!Markings.SequenceEqual(other.Markings)) return false;
return true;
if (maybeOther is not HumanoidCharacterAppearance other)
return false;
if (HairStyleId != other.HairStyleId)
return false;
if (!HairColor.Equals(other.HairColor))
return false;
if (FacialHairStyleId != other.FacialHairStyleId)
return false;
if (!FacialHairColor.Equals(other.FacialHairColor))
return false;
if (!EyeColor.Equals(other.EyeColor))
return false;
if (!SkinColor.Equals(other.SkinColor))
return false;
return Markings.SequenceEqual(other.Markings);
}
}
}

View File

@@ -23,6 +23,10 @@ namespace Content.Shared.Humanoid.Markings
[DataField("sexRestriction")]
public Sex? SexRestriction { get; private set; }
// WD-EDIT
[DataField("sponsorOnly")] public bool SponsorOnly;
// WD-EDIT
[DataField("followSkinColor")]
public bool FollowSkinColor { get; private set; } = false;

View File

@@ -4,7 +4,6 @@ using System.Linq;
using Content.Shared.Humanoid.Prototypes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Humanoid.Markings;
@@ -265,6 +264,37 @@ public sealed partial class MarkingSet
}
}
// WD-EDIT
public void FilterSponsor(string[] sponsorMarkings, MarkingManager? markingManager = null, IPrototypeManager? prototypeManager = null)
{
IoCManager.Resolve(ref markingManager);
IoCManager.Resolve(ref prototypeManager);
var toRemove = new List<(MarkingCategories category, string id)>();
foreach (var (category, list) in Markings)
{
foreach (var marking in list)
{
if (prototypeManager.TryIndex<MarkingPrototype>(marking.MarkingId, out var proto) && !proto.SponsorOnly)
{
return;
}
var allowedToHave = sponsorMarkings.Contains(marking.MarkingId);
if (!allowedToHave)
{
toRemove.Add((category, marking.MarkingId));
}
}
}
foreach (var marking in toRemove)
{
Remove(marking.category, marking.id);
}
}
// WD-EDIT
/// <summary>
/// Ensures that the default markings as defined by the marking point set in this marking set are applied.
/// </summary>

View File

@@ -350,7 +350,7 @@ namespace Content.Shared.Preferences
return Appearance.MemberwiseEquals(other.Appearance);
}
public void EnsureValid()
public void EnsureValid(string[] sponsorMarkings) //WD-EDIT
{
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
@@ -433,7 +433,9 @@ namespace Content.Shared.Preferences
flavortext = FormattedMessage.RemoveMarkup(FlavorText);
}
var appearance = HumanoidCharacterAppearance.EnsureValid(Appearance, Species, Sex);
// WD-EDIT
var appearance = HumanoidCharacterAppearance.EnsureValid(Appearance, Species, sponsorMarkings);
// WD-EDIT
var prefsUnavailableMode = PreferenceUnavailable switch
{

View File

@@ -13,6 +13,6 @@ namespace Content.Shared.Preferences
/// <summary>
/// Makes this profile valid so there's no bad data like negative ages.
/// </summary>
void EnsureValid();
void EnsureValid(string[] sponsorMarkings); //WD-EDIT
}
}

View File

@@ -0,0 +1,36 @@
using Lidgren.Network;
using Robust.Shared.Network;
using Robust.Shared.Serialization;
namespace Content.Shared.White.JoinQueue;
/// <summary>
/// Sent from server to client with queue state for player
/// Also initiates queue state on client
/// </summary>
public sealed class MsgQueueUpdate : NetMessage
{
public override MsgGroups MsgGroup => MsgGroups.Command;
/// <summary>
/// Total players in queue
/// </summary>
public int Total { get; set; }
/// <summary>
/// Player current position in queue (starts from 1)
/// </summary>
public int Position { get; set; }
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
Total = buffer.ReadInt32();
Position = buffer.ReadInt32();
}
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
{
buffer.Write(Total);
buffer.Write(Position);
}
}

View File

@@ -0,0 +1,62 @@
using System.IO;
using System.Text.Json.Serialization;
using Lidgren.Network;
using Robust.Shared.Network;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.White.Sponsors;
[Serializable, NetSerializable]
public sealed class SponsorInfo
{
[JsonPropertyName("tier")]
public int? Tier { get; set; }
[JsonPropertyName("oocColor")]
public string? OOCColor { get; set; }
[JsonPropertyName("priorityJoin")]
public bool HavePriorityJoin { get; set; } = false;
[JsonPropertyName("allowedMarkings")]
public string[] AllowedMarkings { get; set; } = Array.Empty<string>();
}
/// <summary>
/// Server sends sponsoring info to client on connect only if user is sponsor
/// </summary>
public sealed class MsgSponsorInfo : NetMessage
{
public override MsgGroups MsgGroup => MsgGroups.Command;
public SponsorInfo? Info;
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
{
var isSponsor = buffer.ReadBoolean();
buffer.ReadPadBits();
if (!isSponsor)
return;
var length = buffer.ReadVariableInt32();
using var stream = new MemoryStream(length);
buffer.ReadAlignedMemory(stream, length);
serializer.DeserializeDirect(stream, out Info);
}
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
{
buffer.Write(Info != null);
buffer.WritePadBits();
if (Info == null)
return;
var stream = new MemoryStream();
serializer.SerializeDirect(stream, Info);
buffer.WriteVariableInt32((int) stream.Length);
buffer.Write(stream.AsSpan());
}
}

View File

@@ -18,4 +18,20 @@ public sealed class WhiteCVars
public static readonly CVarDef<bool> ChatSlangFilter =
CVarDef.Create("ic.slang_filter", true, CVar.SERVER | CVar.REPLICATED | CVar.ARCHIVE);
/*
* Sponsors
*/
public static readonly CVarDef<string> SponsorsApiUrl =
CVarDef.Create("sponsor.api_url", "", CVar.SERVERONLY);
/*
* Queue
*/
public static readonly CVarDef<bool>
QueueEnabled = CVarDef.Create("queue.enabled", false, CVar.SERVERONLY);
}

View File

@@ -3,6 +3,7 @@
bodyPart: HeadTop
markingCategory: HeadTop
speciesRestriction: [Human]
sponsorOnly: true
coloring:
default:
type:
@@ -26,6 +27,7 @@
bodyPart: Tail
markingCategory: Tail
speciesRestriction: [Human]
sponsorOnly: true
coloring:
default:
type:

View File

@@ -21,6 +21,8 @@
Hair: MobHumanoidAnyMarking
FacialHair: MobHumanoidAnyMarking
Chest: MobHumanTorso
HeadTop: MobHumanoidAnyMarking
Tail: MobHumanoidAnyMarking
Eyes: MobHumanoidEyes
LArm: MobHumanLArm
RArm: MobHumanRArm
@@ -41,10 +43,10 @@
points: 1
required: false
Tail: # the cat tail joke
points: 0
points: 1
required: false
HeadTop: # the cat ear joke
points: 0
points: 1
required: false
Chest:
points: 1