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