mirror of
https://github.com/JogmansonReal/Radicade.git
synced 2026-10-26 07:29:00 -07:00
356 lines
14 KiB
C#
356 lines
14 KiB
C#
using System.Reflection;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using RadicadeServer.DB;
|
|
using RadicadeServer.RecNet.Avatar;
|
|
using RadicadeServer.RecNet.Enums.Players.Avatar;
|
|
using RadicadeServer.RecNet.Enums.Store;
|
|
using RadicadeServer.RecNet.Store;
|
|
|
|
namespace RadicadeServer.RecNet.Storefront
|
|
{
|
|
public static class StorefrontCatalog
|
|
{
|
|
public const string ResourcePrefix = "StorefrontCaptures/";
|
|
|
|
public const string FileSearchPattern = "sf*.json";
|
|
|
|
private static readonly object Gate = new();
|
|
private static readonly Dictionary<long, StorefrontCapture> Captures = new();
|
|
private static readonly Dictionary<int, (long StorefrontId, int Index)> ByPurchasableItemId = new();
|
|
private static bool _loaded;
|
|
|
|
public static Func<IReadOnlyList<string>> FileSource { get; set; } = DiscoverFiles;
|
|
|
|
public static bool IsLoaded
|
|
{
|
|
get { lock (Gate) return _loaded; }
|
|
}
|
|
|
|
public static IReadOnlyCollection<long> StorefrontIds
|
|
{
|
|
get
|
|
{
|
|
Load();
|
|
lock (Gate) return Captures.Keys.OrderBy(id => id).ToArray();
|
|
}
|
|
}
|
|
|
|
public static int Load()
|
|
{
|
|
lock (Gate)
|
|
{
|
|
if (_loaded) return Captures.Count;
|
|
|
|
var captures = ReadCaptures();
|
|
foreach (var capture in captures)
|
|
{
|
|
Captures[capture.StorefrontId] = capture.Capture;
|
|
for (var index = 0; index < capture.Capture.StoreItems.Count; index++)
|
|
{
|
|
var item = capture.Capture.StoreItems[index];
|
|
if (item.PurchasableItemId <= 0) continue;
|
|
ByPurchasableItemId.TryAdd(item.PurchasableItemId, (capture.StorefrontId, index));
|
|
}
|
|
}
|
|
|
|
_loaded = true;
|
|
|
|
if (captures.Count == 0)
|
|
{
|
|
return Captures.Count;
|
|
}
|
|
|
|
var fromFiles = captures.Where(capture => capture.FromFile).Select(capture => capture.StorefrontId).OrderBy(id => id).ToList();
|
|
return Captures.Count;
|
|
}
|
|
}
|
|
|
|
public static List<PurchasableGiftDrop> GetItems(long storefrontId)
|
|
{
|
|
Load();
|
|
lock (Gate)
|
|
{
|
|
return Captures.TryGetValue(storefrontId, out var capture)
|
|
? ToItems(capture)
|
|
: new List<PurchasableGiftDrop>();
|
|
}
|
|
}
|
|
|
|
public static PurchasableGiftDrop? FindItem(long storefrontId, int purchasableItemId)
|
|
{
|
|
Load();
|
|
lock (Gate)
|
|
{
|
|
if (!Captures.TryGetValue(storefrontId, out var capture)) return null;
|
|
var item = capture.StoreItems.FirstOrDefault(x => x.PurchasableItemId == purchasableItemId);
|
|
return item == null ? null : ToItem(item, capture);
|
|
}
|
|
}
|
|
|
|
public static PurchasableGiftDrop? FindItem(int purchasableItemId)
|
|
{
|
|
Load();
|
|
lock (Gate)
|
|
{
|
|
if (!ByPurchasableItemId.TryGetValue(purchasableItemId, out var location)) return null;
|
|
if (!Captures.TryGetValue(location.StorefrontId, out var capture)) return null;
|
|
if (location.Index >= capture.StoreItems.Count) return null;
|
|
return ToItem(capture.StoreItems[location.Index], capture);
|
|
}
|
|
}
|
|
|
|
|
|
public sealed record LoadedCapture(long StorefrontId, string Source, bool FromFile, StorefrontCapture Capture);
|
|
|
|
public static List<LoadedCapture> ReadCaptures()
|
|
{
|
|
var loaded = new List<LoadedCapture>();
|
|
|
|
foreach (var (name, json) in EmbeddedCaptures())
|
|
{
|
|
var capture = ParseCapture(json, name);
|
|
if (capture == null) continue;
|
|
var storefrontId = ResolveStorefrontId(capture, name);
|
|
if (storefrontId == null)
|
|
{
|
|
continue;
|
|
}
|
|
loaded.Add(new LoadedCapture(storefrontId.Value, name, FromFile: false, capture));
|
|
}
|
|
|
|
foreach (var path in FileSource())
|
|
{
|
|
var name = Path.GetFileName(path);
|
|
var capture = ReadCapture(path);
|
|
if (capture == null) continue;
|
|
|
|
var storefrontId = ResolveStorefrontId(capture, name);
|
|
if (storefrontId == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var replaced = loaded.FindIndex(existing => existing.StorefrontId == storefrontId.Value);
|
|
if (replaced >= 0)
|
|
{
|
|
loaded[replaced] = new LoadedCapture(storefrontId.Value, path, FromFile: true, capture);
|
|
}
|
|
else
|
|
{
|
|
loaded.Add(new LoadedCapture(storefrontId.Value, path, FromFile: true, capture));
|
|
}
|
|
}
|
|
|
|
return loaded.OrderBy(capture => capture.StorefrontId).ToList();
|
|
}
|
|
|
|
public static IReadOnlyList<(string Name, string Json)> EmbeddedCaptures()
|
|
{
|
|
var assembly = typeof(StorefrontCatalog).Assembly;
|
|
var payloads = new List<(string Name, string Json)>();
|
|
|
|
foreach (var resource in assembly.GetManifestResourceNames()
|
|
.Where(name => name.StartsWith(ResourcePrefix, StringComparison.Ordinal))
|
|
.OrderBy(name => name, StringComparer.Ordinal))
|
|
{
|
|
using var stream = assembly.GetManifestResourceStream(resource);
|
|
if (stream == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
using var reader = new StreamReader(stream);
|
|
payloads.Add((resource[ResourcePrefix.Length..], reader.ReadToEnd()));
|
|
}
|
|
|
|
return payloads;
|
|
}
|
|
|
|
public static StorefrontCapture? ReadCapture(string path)
|
|
{
|
|
try
|
|
{
|
|
return ParseCapture(File.ReadAllText(path), Path.GetFileName(path));
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static StorefrontCapture? ParseCapture(string json, string sourceName)
|
|
{
|
|
try
|
|
{
|
|
var capture = JsonSerializer.Deserialize<StorefrontCapture>(json, JsonOptions);
|
|
if (capture == null)
|
|
{
|
|
return null;
|
|
}
|
|
return capture;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public static long? ResolveStorefrontId(StorefrontCapture capture, string path)
|
|
{
|
|
if (capture.StorefrontType is > 0) return capture.StorefrontType.Value;
|
|
return StorefrontIdFromFileName(path);
|
|
}
|
|
|
|
public static long? StorefrontIdFromFileName(string path)
|
|
{
|
|
var name = Path.GetFileNameWithoutExtension(path);
|
|
if (!name.StartsWith("sf", StringComparison.OrdinalIgnoreCase)) return null;
|
|
var digits = name[2..].Trim();
|
|
return int.TryParse(digits, out var id) && id > 0 ? id : null;
|
|
}
|
|
|
|
|
|
public sealed class StorefrontCapture
|
|
{
|
|
[JsonPropertyName("StorefrontType")] public int? StorefrontType { get; set; }
|
|
[JsonPropertyName("NextUpdate")] public DateTime? NextUpdate { get; set; }
|
|
[JsonPropertyName("NewUntil")] public DateTime? NewUntil { get; set; }
|
|
[JsonPropertyName("SubscriberDiscountPercent")] public int SubscriberDiscountPercent { get; set; }
|
|
[JsonPropertyName("StoreItems")] public List<StorefrontCaptureItem> StoreItems { get; set; } = new();
|
|
}
|
|
|
|
public sealed class StorefrontCaptureItem
|
|
{
|
|
[JsonPropertyName("PurchasableItemId")] public int PurchasableItemId { get; set; }
|
|
[JsonPropertyName("Type")] public int Type { get; set; }
|
|
[JsonPropertyName("IsFeatured")] public bool IsFeatured { get; set; }
|
|
[JsonPropertyName("NewUntil")] public DateTime? NewUntil { get; set; }
|
|
[JsonPropertyName("Prices")] public List<StorefrontCapturePrice> Prices { get; set; } = new();
|
|
[JsonPropertyName("SubscriberPrices")] public List<StorefrontCapturePrice> SubscriberPrices { get; set; } = new();
|
|
[JsonPropertyName("GiftDrop")] public StorefrontCaptureGift? GiftDrop { get; set; }
|
|
}
|
|
|
|
public sealed class StorefrontCapturePrice
|
|
{
|
|
[JsonPropertyName("CurrencyType")] public int CurrencyType { get; set; }
|
|
[JsonPropertyName("Price")] public int Price { get; set; }
|
|
}
|
|
|
|
public sealed class StorefrontCaptureGift
|
|
{
|
|
[JsonPropertyName("GiftDropId")] public long GiftDropId { get; set; }
|
|
[JsonPropertyName("FriendlyName")] public string? FriendlyName { get; set; }
|
|
[JsonPropertyName("Tooltip")] public string? Tooltip { get; set; }
|
|
[JsonPropertyName("ConsumableItemDesc")] public string? ConsumableItemDesc { get; set; }
|
|
[JsonPropertyName("AvatarItemDesc")] public string? AvatarItemDesc { get; set; }
|
|
[JsonPropertyName("AvatarItemType")] public int? AvatarItemType { get; set; }
|
|
[JsonPropertyName("EquipmentPrefabName")] public string? EquipmentPrefabName { get; set; }
|
|
[JsonPropertyName("EquipmentModificationGuid")] public string? EquipmentModificationGuid { get; set; }
|
|
[JsonPropertyName("IsQuery")] public bool IsQuery { get; set; }
|
|
[JsonPropertyName("Unique")] public bool Unique { get; set; }
|
|
[JsonPropertyName("SubscribersOnly")] public bool SubscribersOnly { get; set; }
|
|
[JsonPropertyName("Rarity")] public int? Rarity { get; set; }
|
|
[JsonPropertyName("Context")] public int? Context { get; set; }
|
|
[JsonPropertyName("Currency")] public int Currency { get; set; }
|
|
[JsonPropertyName("CurrencyType")] public int? CurrencyType { get; set; }
|
|
[JsonPropertyName("ItemSetId")] public long? ItemSetId { get; set; }
|
|
[JsonPropertyName("ItemSetFriendlyName")] public string? ItemSetFriendlyName { get; set; }
|
|
}
|
|
|
|
|
|
public static List<PurchasableGiftDrop> ToItems(StorefrontCapture capture)
|
|
{
|
|
var items = new List<PurchasableGiftDrop>(capture.StoreItems.Count);
|
|
foreach (var item in capture.StoreItems)
|
|
{
|
|
var mapped = ToItem(item, capture);
|
|
if (mapped != null) items.Add(mapped);
|
|
}
|
|
return items;
|
|
}
|
|
|
|
public static PurchasableGiftDrop? ToItem(StorefrontCaptureItem item, StorefrontCapture capture)
|
|
{
|
|
var gift = item.GiftDrop;
|
|
if (gift == null || !HasContent(gift)) return null;
|
|
|
|
return new PurchasableGiftDrop
|
|
{
|
|
PurchasableItemId = item.PurchasableItemId,
|
|
Type = Enum.IsDefined(typeof(PurchasableItem.PurchasableItemType), item.Type)
|
|
? (PurchasableItem.PurchasableItemType)item.Type
|
|
: PurchasableItem.PurchasableItemType.GiftDrop,
|
|
IsFeatured = item.IsFeatured,
|
|
Prices = item.Prices.Select(ToPrice).ToList(),
|
|
SubscriberPrices = item.SubscriberPrices.Select(ToPrice).ToList(),
|
|
EndDate = null,
|
|
NewUntil = item.NewUntil,
|
|
GiftDrops = new List<Gift>
|
|
{
|
|
new()
|
|
{
|
|
GiftDropId = gift.GiftDropId,
|
|
FriendlyName = gift.FriendlyName?.Trim() ?? string.Empty,
|
|
Tooltip = gift.Tooltip?.Trim() ?? string.Empty,
|
|
ConsumableItemDesc = gift.ConsumableItemDesc ?? string.Empty,
|
|
AvatarItemDesc = gift.AvatarItemDesc ?? string.Empty,
|
|
AvatarItemType = gift.AvatarItemType is null
|
|
? AvatarItemType.Outfit
|
|
: (AvatarItemType)gift.AvatarItemType,
|
|
EquipmentPrefabName = gift.EquipmentPrefabName ?? string.Empty,
|
|
EquipmentModificationGuid = gift.EquipmentModificationGuid ?? string.Empty,
|
|
IsQuery = gift.IsQuery,
|
|
Unique = gift.Unique,
|
|
SubscribersOnly = gift.SubscribersOnly,
|
|
Rarity = (Rarity)(gift.Rarity ?? (int)Rarity.Common),
|
|
Context = (GiftContext)(gift.Context ?? (int)GiftContext.Commerce_Purchase),
|
|
Currency = gift.Currency,
|
|
CurrencyType = (CurrencyType)(gift.CurrencyType ?? (int)CurrencyType.RecCenterTokens),
|
|
ItemSetId = gift.ItemSetId ?? 0,
|
|
ItemSetFriendlyName = gift.ItemSetFriendlyName ?? string.Empty
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
private static PurchasablePrice ToPrice(StorefrontCapturePrice price) => new()
|
|
{
|
|
CurrencyType = (CurrencyType)price.CurrencyType,
|
|
Price = price.Price
|
|
};
|
|
|
|
private static bool HasContent(StorefrontCaptureGift gift) =>
|
|
!string.IsNullOrWhiteSpace(gift.FriendlyName)
|
|
|| !string.IsNullOrWhiteSpace(gift.AvatarItemDesc)
|
|
|| !string.IsNullOrWhiteSpace(gift.ConsumableItemDesc)
|
|
|| !string.IsNullOrWhiteSpace(gift.EquipmentPrefabName);
|
|
|
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
{
|
|
PropertyNameCaseInsensitive = true,
|
|
ReadCommentHandling = JsonCommentHandling.Skip,
|
|
AllowTrailingCommas = true
|
|
};
|
|
|
|
private static IReadOnlyList<string> DiscoverFiles()
|
|
{
|
|
var files = new List<string>();
|
|
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
foreach (var directory in DBConfig.StorefrontDirectories())
|
|
{
|
|
if (!Directory.Exists(directory)) continue;
|
|
|
|
foreach (var path in Directory.GetFiles(directory, FileSearchPattern))
|
|
{
|
|
if (!seen.Add(Path.GetFileName(path))) continue;
|
|
files.Add(path);
|
|
}
|
|
}
|
|
|
|
return files.OrderBy(path => StorefrontIdFromFileName(path) ?? long.MaxValue).ToList();
|
|
}
|
|
}
|
|
}
|