mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-27 07:59:03 -07:00
Radicade Public Code
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user