mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-25 15:09:26 -07:00
257 lines
8.3 KiB
C#
257 lines
8.3 KiB
C#
using RadicadeServer.Extensions;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
|
|
namespace RadicadeServer.RecNet.Avatar;
|
|
|
|
public static class AvatarUnlockCatalog
|
|
{
|
|
public static string LoadStarterJson()
|
|
{
|
|
using var stream = typeof(AvatarUnlockCatalog).Assembly.GetManifestResourceStream("StarterAvatarItems.json")
|
|
?? throw new InvalidOperationException("The starter avatar inventory resource is missing.");
|
|
using var reader = new StreamReader(stream);
|
|
return reader.ReadToEnd();
|
|
}
|
|
|
|
public static List<AvatarItem> GetStarterItems()
|
|
{
|
|
var starters = EnrichWithMetadata(ParseCatalog(LoadStarterJson()), DBConfig.DefaultAvatarCatalog);
|
|
starters.AddRange(ParseCatalog(DBConfig.DefaultAvatarCatalog).Where(item => item.IsBaseAvatarItem));
|
|
return KeepClientAvailableItems(KeepResolvableItems(starters))
|
|
.DistinctBy(item => item.AvatarItemDesc, StringComparer.OrdinalIgnoreCase).ToList();
|
|
}
|
|
|
|
private static readonly JsonSerializerOptions CatalogOptions = new()
|
|
{
|
|
PropertyNameCaseInsensitive = true,
|
|
AllowTrailingCommas = true,
|
|
ReadCommentHandling = JsonCommentHandling.Skip,
|
|
NumberHandling = JsonNumberHandling.AllowReadingFromString
|
|
};
|
|
|
|
public static List<AvatarItem> ParseCatalog(string? json)
|
|
{
|
|
var items = new List<AvatarItem>();
|
|
if (string.IsNullOrWhiteSpace(json)) return items;
|
|
try
|
|
{
|
|
using var document = JsonDocument.Parse(json, new JsonDocumentOptions
|
|
{
|
|
AllowTrailingCommas = true,
|
|
CommentHandling = JsonCommentHandling.Skip
|
|
});
|
|
if (document.RootElement.ValueKind != JsonValueKind.Array)
|
|
{
|
|
return items;
|
|
}
|
|
var index = 0;
|
|
foreach (var entry in document.RootElement.EnumerateArray())
|
|
{
|
|
try
|
|
{
|
|
var item = entry.Deserialize<AvatarItem>(CatalogOptions);
|
|
if (item != null && !string.IsNullOrWhiteSpace(item.AvatarItemDesc))
|
|
items.Add(item);
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
}
|
|
index++;
|
|
}
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
}
|
|
return KeepSupportedItems(items);
|
|
}
|
|
|
|
public static bool NeedsMetadataEnrichment(IEnumerable<AvatarItem>? items)
|
|
{
|
|
return items != null && items.Any(item =>
|
|
item == null || item.AvatarItemId <= 0 || string.IsNullOrWhiteSpace(item.ThumbnailImage));
|
|
}
|
|
|
|
public static List<AvatarItem> EnrichWithMetadata(IEnumerable<AvatarItem> items, string? metadataJson)
|
|
{
|
|
var metadata = ParseCatalog(metadataJson).ToDictionary(x => x.AvatarItemDesc, StringComparer.OrdinalIgnoreCase);
|
|
return items.Select(item => metadata.TryGetValue(item.AvatarItemDesc, out var full)
|
|
? new AvatarItem
|
|
{
|
|
AvatarItemDesc = item.AvatarItemDesc, UnlockedLevel = item.UnlockedLevel,
|
|
PlatformMask = item.PlatformMask, FriendlyName = full.FriendlyName,
|
|
Tooltip = full.Tooltip, Rarity = full.Rarity, AvatarItemType = full.AvatarItemType,
|
|
AvatarItemId = full.AvatarItemId, IsBaseAvatarItem = full.IsBaseAvatarItem,
|
|
TagList = full.TagList, CreatedAt = full.CreatedAt, ThumbnailImage = full.ThumbnailImage
|
|
}
|
|
: item).ToList();
|
|
}
|
|
private static readonly string[] KnownUnsafeDescriptionParts =
|
|
[
|
|
"07116e9b-b182-40b4-90c2-99738e7aedd6",
|
|
"11b3413f-2c18-41cb-8ef2-55fb825ed476",
|
|
"UjSxxUK7z0GGdjvI_NsJ3Q",
|
|
"X0nzvhAtgEKqzjQPb9d3qQ",
|
|
"6taO0KRM_kuKoaTnOIsHRg",
|
|
"KUAMuM41hk-YLZoqTiKncA,LjQ1UKN2xkqJkok40Dxaew",
|
|
"5aa7be74-19a9-4dd8-b525-46c5aef34866",
|
|
];
|
|
|
|
private static readonly Lazy<List<AvatarItem>> SafeDefaultItems = new(() =>
|
|
DBConfig.SafeDefaultAvatarUnlocks2023.FromJson<List<AvatarItem>>()
|
|
?? new List<AvatarItem>());
|
|
private static readonly Lazy<HashSet<string>> SafeDefaultDescriptions = new(() =>
|
|
SafeDefaultItems.Value
|
|
.Where(item => !string.IsNullOrWhiteSpace(item.AvatarItemDesc))
|
|
.Select(item => item.AvatarItemDesc)
|
|
.ToHashSet(StringComparer.OrdinalIgnoreCase));
|
|
|
|
public static List<AvatarItem> GetSafeDefaultItems()
|
|
{
|
|
var catalogJson = string.Equals(DBConfig.DefaultAvatarCatalog, DBConfig.SafeDefaultAvatarUnlocks2023,
|
|
StringComparison.Ordinal) ? DBConfig.DefaultAvatarUnlocks : DBConfig.DefaultAvatarCatalog;
|
|
var items = ParseCatalog(catalogJson);
|
|
if (items.Count == 0)
|
|
items = ParseCatalog(DBConfig.DefaultAvatarUnlocks);
|
|
var supportedItems = KeepResolvableItems(items);
|
|
var candidates = supportedItems.Count > 0
|
|
? supportedItems
|
|
: SafeDefaultItems.Value.Select(Clone).ToList();
|
|
var available = KeepClientAvailableItems(candidates);
|
|
return available.Count > 0 ? available : candidates;
|
|
}
|
|
|
|
public static List<AvatarItem> KeepClientAvailableItems(IEnumerable<AvatarItem>? items)
|
|
{
|
|
if (items == null)
|
|
{
|
|
return new List<AvatarItem>();
|
|
}
|
|
|
|
var cutoff = DBConfig.AvatarItemReleaseCutoffUtc;
|
|
if (cutoff == default)
|
|
{
|
|
return items.Where(item => item != null).ToList();
|
|
}
|
|
|
|
return items.Where(item => item != null && IsAvailableToClient(item, cutoff)).ToList();
|
|
}
|
|
|
|
public static bool IsAvailableToClient(AvatarItem item)
|
|
{
|
|
return IsAvailableToClient(item, DBConfig.AvatarItemReleaseCutoffUtc);
|
|
}
|
|
|
|
private static bool IsAvailableToClient(AvatarItem? item, DateTime cutoffUtc)
|
|
{
|
|
if (item == null || item.CreatedAt == default)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var createdAt = item.CreatedAt.Kind == DateTimeKind.Unspecified
|
|
? DateTime.SpecifyKind(item.CreatedAt, DateTimeKind.Utc)
|
|
: item.CreatedAt.ToUniversalTime();
|
|
|
|
return createdAt < cutoffUtc;
|
|
}
|
|
|
|
public static List<AvatarItem> KeepSupportedItems(IEnumerable<AvatarItem>? items)
|
|
{
|
|
if (items == null)
|
|
{
|
|
return new List<AvatarItem>();
|
|
}
|
|
|
|
return items
|
|
.Where(item =>
|
|
item != null &&
|
|
!string.IsNullOrWhiteSpace(item.AvatarItemDesc) &&
|
|
!IsKnownUnsafe(item.AvatarItemDesc))
|
|
.Select(Clone)
|
|
.ToList();
|
|
}
|
|
|
|
public static List<AvatarItem> KeepResolvableItems(IEnumerable<AvatarItem>? items)
|
|
{
|
|
var supportedItems = KeepSupportedItems(items);
|
|
var resolvable = DBConfig.ResolvableAvatarItemDescs;
|
|
if (resolvable == null || resolvable.Count == 0)
|
|
{
|
|
return supportedItems;
|
|
}
|
|
|
|
return supportedItems.Where(item => IsClientResolvableDescription(item.AvatarItemDesc)).ToList();
|
|
}
|
|
|
|
public static bool IsClientResolvableDescription(string? description)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(description)) return false;
|
|
|
|
var resolvable = DBConfig.ResolvableAvatarItemDescs;
|
|
return resolvable == null || resolvable.Count == 0 ||
|
|
resolvable.Contains(AvatarItemDescription.Normalize(description));
|
|
}
|
|
|
|
public static AvatarItem? FindItem(string? avatarItemDesc, string? catalogueJson = null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(avatarItemDesc)) return null;
|
|
|
|
var wanted = AvatarItemDescription.Normalize(avatarItemDesc);
|
|
if (string.IsNullOrWhiteSpace(wanted)) return null;
|
|
|
|
var json = string.IsNullOrWhiteSpace(catalogueJson) ? DBConfig.DefaultAvatarCatalog : catalogueJson;
|
|
return CatalogueByDescription(json).TryGetValue(wanted, out var item) ? Clone(item) : null;
|
|
}
|
|
|
|
private static string? _byDescriptionCacheKey;
|
|
private static Dictionary<string, AvatarItem>? _byDescriptionCache;
|
|
|
|
private static Dictionary<string, AvatarItem> CatalogueByDescription(string? json)
|
|
{
|
|
if (_byDescriptionCache != null && string.Equals(_byDescriptionCacheKey, json, StringComparison.Ordinal))
|
|
return _byDescriptionCache;
|
|
|
|
var byDescription = new Dictionary<string, AvatarItem>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (var item in ParseCatalog(json))
|
|
{
|
|
if (item == null || string.IsNullOrWhiteSpace(item.AvatarItemDesc)) continue;
|
|
if (!byDescription.ContainsKey(item.AvatarItemDesc)) byDescription[item.AvatarItemDesc] = item;
|
|
}
|
|
|
|
_byDescriptionCache = byDescription;
|
|
_byDescriptionCacheKey = json;
|
|
return byDescription;
|
|
}
|
|
|
|
public static bool IsKnownSafe(string avatarItemDesc)
|
|
{
|
|
return SafeDefaultDescriptions.Value.Contains(avatarItemDesc);
|
|
}
|
|
|
|
public static bool IsKnownUnsafe(string avatarItemDesc)
|
|
{
|
|
return KnownUnsafeDescriptionParts.Any(part =>
|
|
avatarItemDesc.Contains(part, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
private static AvatarItem Clone(AvatarItem item)
|
|
{
|
|
return new AvatarItem
|
|
{
|
|
AvatarItemDesc = AvatarItemDescription.Normalize(item.AvatarItemDesc),
|
|
UnlockedLevel = item.UnlockedLevel,
|
|
PlatformMask = item.PlatformMask,
|
|
FriendlyName = item.FriendlyName,
|
|
Tooltip = item.Tooltip,
|
|
Rarity = item.Rarity,
|
|
AvatarItemType = item.AvatarItemType,
|
|
AvatarItemId = item.AvatarItemId,
|
|
IsBaseAvatarItem = item.IsBaseAvatarItem,
|
|
TagList = item.TagList,
|
|
CreatedAt = item.CreatedAt,
|
|
ThumbnailImage = item.ThumbnailImage
|
|
};
|
|
}
|
|
}
|