Simple sponsorship system (#1189)

* simple sponsorship system

* priority Join

* OOC Sponsor Color

* Update CP14SponsorRolePrototype.cs

* loadout sponsorship

* bruh

* refactor to interfaces

* Update CP14ClientSponsorManager.cs

* finish loadout option

* role pass
This commit is contained in:
Ed
2025-04-17 19:55:25 +03:00
committed by GitHub
parent 1a0136c21f
commit 39ca6175e6
29 changed files with 491 additions and 21 deletions

View File

@@ -7,6 +7,7 @@ using Content.Server.Administration.Systems;
using Content.Server.MoMMI;
using Content.Server.Players.RateLimiting;
using Content.Server.Preferences.Managers;
using Content.Shared._CP14.Sponsor;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Chat;
@@ -44,6 +45,7 @@ internal sealed partial class ChatManager : IChatManager
[Dependency] private readonly INetConfigurationManager _netConfigManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly PlayerRateLimitManager _rateLimitManager = default!;
[Dependency] private readonly ICP14SponsorManager _sponsor = default!; //CP14 OCC color
/// <summary>
/// The maximum length a player-sent message can be sent
@@ -256,6 +258,12 @@ internal sealed partial class ChatManager : IChatManager
{
wrappedMessage = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", patronColor),("playerName", player.Name), ("message", FormattedMessage.EscapeText(message)));
}
//CP14 Sponsor OOC Color
if (_sponsor.TryGetSponsorOOCColor(player.UserId, out var oocColor))
{
wrappedMessage = Loc.GetString("chat-manager-send-ooc-patron-wrap-message", ("patronColor", oocColor),("playerName", player.Name), ("message", FormattedMessage.EscapeText(message)));
}
//CP14 Sponsor OOC Color end
//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);

View File

@@ -2,12 +2,14 @@ using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Content.Server._CP14.Sponsor;
using Content.Server.Administration.Managers;
using Content.Server.Chat.Managers;
using Content.Server.Connection.IPIntel;
using Content.Server.Database;
using Content.Server.GameTicking;
using Content.Server.Preferences.Managers;
using Content.Shared._CP14.Sponsor;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Players.PlayTimeTracking;
@@ -64,6 +66,7 @@ namespace Content.Server.Connection
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IHttpClientHolder _http = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly ICP14SponsorManager _sponsor = default!; //CP14 Priority Join
private ISawmill _sawmill = default!;
private readonly Dictionary<NetUserId, TimeSpan> _temporaryBypasses = [];
@@ -375,11 +378,11 @@ namespace Content.Server.Connection
public async Task<bool> HavePrivilegedJoin(NetUserId userId)
{
var adminBypass = _cfg.GetCVar(CCVars.AdminBypassMaxPlayers) && await _db.GetAdminDataForAsync(userId) != null;
//var havePriorityJoin = _sponsors
var havePriorityJoin = _sponsor.UserHasFeature(userId, "PriorityJoin", false);
var wasInGame = EntitySystem.TryGet<GameTicker>(out var ticker) &&
ticker.PlayerGameStatuses.TryGetValue(userId, out var status) &&
status == PlayerGameStatus.JoinedGame;
return adminBypass || wasInGame;
return adminBypass || wasInGame || havePriorityJoin;
}
//CP14 Join Queue end
}

View File

@@ -1,5 +1,6 @@
using Content.Server._CP14.Discord;
using Content.Server._CP14.JoinQueue;
using Content.Server._CP14.Sponsor;
using Content.Server.Acz;
using Content.Server.Administration;
using Content.Server.Administration.Logs;
@@ -25,6 +26,7 @@ using Content.Server.Preferences.Managers;
using Content.Server.ServerInfo;
using Content.Server.ServerUpdates;
using Content.Server.Voting.Managers;
using Content.Shared._CP14.Sponsor;
using Content.Shared.CCVar;
using Content.Shared.Kitchen;
using Content.Shared.Localizations;
@@ -106,6 +108,7 @@ namespace Content.Server.Entry
//CP14
IoCManager.Resolve<DiscordAuthManager>().Initialize();
IoCManager.Resolve<JoinQueueManager>().Initialize();
IoCManager.Resolve<ICP14SponsorManager>().Initialize();
//CP14 end
IoCManager.Resolve<IAdminLogManager>().Initialize();

View File

@@ -1,5 +1,6 @@
using Content.Server._CP14.Discord;
using Content.Server._CP14.JoinQueue;
using Content.Server._CP14.Sponsor;
using Content.Server.Administration;
using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
@@ -25,6 +26,7 @@ using Content.Server.ServerInfo;
using Content.Server.ServerUpdates;
using Content.Server.Voting.Managers;
using Content.Server.Worldgen.Tools;
using Content.Shared._CP14.Sponsor;
using Content.Shared.Administration.Logs;
using Content.Shared.Administration.Managers;
using Content.Shared.Chat;
@@ -41,6 +43,7 @@ namespace Content.Server.IoC
//CP14
IoCManager.Register<DiscordAuthManager>();
IoCManager.Register<JoinQueueManager>();
IoCManager.Register<ICP14SponsorManager, SponsorSystem>();
//CP14 end
IoCManager.Register<IChatManager, ChatManager>();
IoCManager.Register<ISharedChatManager, ChatManager>();

View File

@@ -201,7 +201,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
playTimes = new Dictionary<string, TimeSpan>();
}
return JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter);
return JobRequirements.TryRequirementsMet(player.UserId, job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter); //CP14 add NetUserId for sponsorship checks
}
public HashSet<ProtoId<JobPrototype>> GetDisallowedJobs(ICommonSession player)
@@ -218,7 +218,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
foreach (var job in _prototypes.EnumeratePrototypes<JobPrototype>())
{
if (JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter))
if (JobRequirements.TryRequirementsMet(player.UserId, job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter)) //CP14 add NetUserId for sponsorship checks
roles.Add(job.ID);
}
@@ -241,7 +241,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
for (var i = 0; i < jobs.Count; i++)
{
if (_prototypes.TryIndex(jobs[i], out var job)
&& JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(userId).SelectedCharacter))
&& JobRequirements.TryRequirementsMet(userId, job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(userId).SelectedCharacter)) //CP14 add NetUserId for sponsorship checks
{
continue;
}

View File

@@ -30,11 +30,10 @@ public sealed class DiscordAuthManager
private string _apiUrl = string.Empty;
private string _apiKey = string.Empty;
private string _discordGuild = "1221923073759121468"; //CrystallEdge server required
public const string DISCORD_GUILD = "1221923073759121468"; //CrystallEdge server required
private HashSet<string> _blockedGuilds = new()
{
"1361786483073093673", //Testing one
"1346922008000204891",
"1186566619858731038",
"1355279097906855968",
@@ -137,7 +136,6 @@ public sealed class DiscordAuthManager
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);
var response = await _httpClient.SendAsync(request, cancel);
_sawmill.Debug($"Guilds response: {await response.Content.ReadAsStringAsync(cancel)}");
_sawmill.Debug($"(int) response.StatusCode: {(int)response.StatusCode}");
if (!response.IsSuccessStatusCode)
{
@@ -161,9 +159,9 @@ public sealed class DiscordAuthManager
}
}
if (guilds.Guilds.All(guild => guild.Id != _discordGuild))
if (guilds.Guilds.All(guild => guild.Id != DISCORD_GUILD))
{
_sawmill.Debug($"Player {userId} is not in required guild {_discordGuild}");
_sawmill.Debug($"Player {userId} is not in required guild {DISCORD_GUILD}");
return new AuthData { Verified = false, ErrorMessage = "You are not a member of the CrystallEdge server." };
}

View File

@@ -40,7 +40,6 @@ public sealed class JoinQueueManager
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IServerNetManager _netManager = default!;
[Dependency] private readonly DiscordAuthManager _discordAuthManager = default!;
//[Dependency] private readonly SponsorsManager _sponsors = default!;
private ISawmill _sawmill = default!;
/// <summary>

View File

@@ -0,0 +1,156 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Content.Server._CP14.Discord;
using Content.Shared._CP14.Sponsor;
using Content.Shared.CCVar;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server._CP14.Sponsor;
public sealed class SponsorSystem : ICP14SponsorManager
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly DiscordAuthManager _discordAuthManager = default!;
[Dependency] private readonly INetManager _netMgr = default!;
[Dependency] private readonly IServerNetManager _netManager = default!;
private readonly HttpClient _httpClient = new();
private string _apiUrl = string.Empty;
private string _apiKey = string.Empty;
private bool _enabled;
private ISawmill _sawmill = null!;
private Dictionary<NetUserId, CP14SponsorRolePrototype> _cachedSponsors = new();
public void Initialize()
{
_sawmill = Logger.GetSawmill("sponsors");
_netManager.RegisterNetMessage<CP14SponsorRoleUpdate>();
_cfg.OnValueChanged(CCVars.SponsorsEnabled, val => { _enabled = val; }, true);
_cfg.OnValueChanged(CCVars.SponsorsApiUrl, val => { _apiUrl = val; }, true);
_cfg.OnValueChanged(CCVars.SponsorsApiKey, val => { _apiKey = val; }, true);
_discordAuthManager.PlayerVerified += async (_, e) =>
{
await OnPlayerVerified(e);
};
_netMgr.Disconnect += OnDisconnect;
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", _apiKey);
}
private async Task<List<string>?> GetRoles(NetUserId userId)
{
var requestUrl = $"{_apiUrl}/api/roles?method=uid&id={userId}&guildId={DiscordAuthManager.DISCORD_GUILD}";
var response = await _httpClient.GetAsync(requestUrl);
if (!response.IsSuccessStatusCode)
{
_sawmill.Error($"Failed to retrieve roles for user {userId}: {response.StatusCode}");
return null;
}
var responseContent = await response.Content.ReadFromJsonAsync<RolesResponse>();
if (responseContent is not null)
{
return responseContent.Roles.ToList();
}
_sawmill.Error($"Roles not found in response for user {userId}");
return null;
}
private async Task OnPlayerVerified(ICommonSession e)
{
if (!_enabled)
return;
var roles = await GetRoles(e.UserId);
if (roles is null)
return;
CP14SponsorRolePrototype? targetRole = null;
foreach (var role in _proto.EnumeratePrototypes<CP14SponsorRolePrototype>())
{
if (!roles.Contains(role.DiscordRoleId))
continue;
if (targetRole is null || role.Priority > targetRole.Priority)
{
targetRole = role;
}
}
if (targetRole is not null)
_cachedSponsors[e.UserId] = targetRole;
if (_cachedSponsors.TryGetValue(e.UserId, out var cachedRole))
{
e.Channel.SendMessage(new CP14SponsorRoleUpdate
{
Role = cachedRole,
});
}
}
private void OnDisconnect(object? sender, NetDisconnectedArgs e)
{
//Remove cached roles
if (_cachedSponsors.ContainsKey(e.Channel.UserId))
{
_cachedSponsors.Remove(e.Channel.UserId);
}
}
public bool UserHasFeature(NetUserId userId,
ProtoId<CP14SponsorFeaturePrototype> feature,
bool ifDisabledSponsorhip = true)
{
if (!_enabled)
return ifDisabledSponsorhip;
if (!_proto.TryIndex(feature, out var indexedFeature))
return false;
if (!_cachedSponsors.TryGetValue(userId, out var userRoles))
return false;
return _cachedSponsors[userId].Priority >= indexedFeature.MinPriority;
}
public bool TryGetSponsorOOCColor(NetUserId userId, [NotNullWhen(true)] out Color? color)
{
color = null;
if (!_enabled)
return false;
if (!_cachedSponsors.TryGetValue(userId, out var sponsorRole))
return false;
color = sponsorRole.Color;
return color is not null;
}
private sealed class RolesResponse
{
[JsonPropertyName("roles")]
public string[] Roles { get; set; } = [];
}
}