using System.Text.Json; using RadicadeServer.DB; namespace RadicadeServer.RecNet.Keepsakes; public sealed class KeepsakeRoomConfig { public const string FileName = "keepsake-rooms.json"; public const string AllRoomsToken = "*"; public const long DefaultRoomListId = 1; public const int DefaultRoomLimit = 10; public const long DefaultEventId = 1; public const string DefaultEventName = "Science Core"; public IReadOnlySet RoomIds { get; init; } = new HashSet(); public bool AllRooms { get; init; } public long RoomListId { get; init; } = DefaultRoomListId; public int RoomLimit { get; init; } = DefaultRoomLimit; public long EventId { get; init; } = DefaultEventId; public string EventName { get; init; } = DefaultEventName; public bool EventEnabled { get; init; } = true; public bool Any => AllRooms || RoomIds.Count > 0; public bool IsKeepsakeRoom(long roomId) => AllRooms || RoomIds.Contains(roomId); public long[] AdvertisedRooms(long roomId) => RoomIds.Count == 0 ? new[] { roomId } : RoomIds.OrderBy(id => id).ToArray(); public long[] ConfiguredRooms => RoomIds.OrderBy(id => id).ToArray(); public object[] RoomListEntries(long[] rooms) => new object[] { new { KeepsakeRoomListId = RoomListId, ProgressionEventId = EventId, UnlockItemAvatarItemId = (int?)null, UnlockItemGiftDropId = (int?)null, UnlockItemLockDurationTicks = (long?)null, RoomUnlockStartOffsetTicks = 0L, RoomUnlockIntervalTicks = TimeSpan.TicksPerDay, RoomUnlockBatchSize = Math.Max(rooms.Length, 1), RoomType = 0, KeepsakeRoomLimit = RoomLimit, KeepsakeRooms = rooms.Select((id, order) => new { KeepsakeRoomId = id, RoomId = id, KeepsakeRoomListId = RoomListId, Type = 0, Order = order }).ToArray(), } }; public static KeepsakeRoomConfig? Override { get; set; } public static KeepsakeRoomConfig Current => Override ?? Load(); public static KeepsakeRoomConfig Load() => Parse( ReadConfigFile(), Environment.GetEnvironmentVariable("RADICADE_KEEPSAKE_ROOMS"), Environment.GetEnvironmentVariable("RADICADE_KEEPSAKE_ROOM_LIST_ID"), Environment.GetEnvironmentVariable("RADICADE_KEEPSAKE_ROOM_LIMIT"), Environment.GetEnvironmentVariable("RADICADE_KEEPSAKE_EVENT_ID"), Environment.GetEnvironmentVariable("RADICADE_KEEPSAKE_EVENT_NAME")); public static IEnumerable CandidatePaths() => DBConfig.AvatarCatalogDirectories() .Select(directory => Path.Join(directory, FileName)) .Distinct(StringComparer.OrdinalIgnoreCase); private static string? ReadConfigFile() { foreach (var path in CandidatePaths()) { try { if (File.Exists(path)) return File.ReadAllText(path); } catch (IOException) { } catch (UnauthorizedAccessException) { } } return null; } public static KeepsakeRoomConfig Parse( string? json, string? rooms = null, string? roomListId = null, string? roomLimit = null, string? eventIdText = null, string? eventNameText = null) { var allRooms = false; var ids = new HashSet(); var listId = DefaultRoomListId; var limit = DefaultRoomLimit; var eventId = DefaultEventId; var eventName = DefaultEventName; var eventEnabled = true; if (!string.IsNullOrWhiteSpace(json)) { try { using var document = JsonDocument.Parse(json); var root = document.RootElement; if (root.ValueKind == JsonValueKind.Array) AddIds(root, ids); else if (root.ValueKind == JsonValueKind.Object) { foreach (var property in root.EnumerateObject()) { switch (property.Name.ToLowerInvariant()) { case "allrooms": allRooms = IsTrue(property.Value); break; case "roomids" or "rooms" or "keepsakerooms": AddIds(property.Value, ids); break; case "roomlistid" or "keepsakeroomlistid": listId = ReadLong(property.Value) ?? listId; break; case "roomlimit" or "keepsakeroomlimit": if (ReadLong(property.Value) is > 0 and <= int.MaxValue and var configuredLimit) limit = (int)configuredLimit; break; case "eventenabled": eventEnabled = IsTrue(property.Value); break; case "eventid" or "progressioneventid": eventId = ReadLong(property.Value) ?? eventId; break; case "eventname" or "progressioneventname": var name = property.Value.ValueKind == JsonValueKind.String ? property.Value.GetString() : null; if (!string.IsNullOrWhiteSpace(name)) eventName = name!.Trim(); break; } } } } catch (JsonException) { } } if (!string.IsNullOrWhiteSpace(rooms)) { ids.Clear(); var text = rooms.Trim(); allRooms = text == AllRoomsToken; if (!allRooms) AddIdsFromText(text, ids); } if (long.TryParse(roomListId, out var parsedListId)) listId = parsedListId; if (int.TryParse(roomLimit, out var parsedLimit) && parsedLimit > 0) limit = parsedLimit; if (long.TryParse(eventIdText, out var parsedEventId)) eventId = parsedEventId; if (!string.IsNullOrWhiteSpace(eventNameText)) eventName = eventNameText.Trim(); return new KeepsakeRoomConfig { RoomIds = ids, AllRooms = allRooms, RoomListId = listId, RoomLimit = limit, EventId = eventId, EventName = eventName, EventEnabled = eventEnabled, }; } private static void AddIds(JsonElement element, HashSet ids) { switch (element.ValueKind) { case JsonValueKind.Array: foreach (var item in element.EnumerateArray()) { if (ReadLong(item) is { } id) ids.Add(id); } break; case JsonValueKind.String: AddIdsFromText(element.GetString(), ids); break; default: if (ReadLong(element) is { } single) ids.Add(single); break; } } private static void AddIdsFromText(string? text, HashSet ids) { if (string.IsNullOrWhiteSpace(text)) return; foreach (var part in text.Split(new[] { ',', ';', ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) { if (long.TryParse(part, out var id)) ids.Add(id); } } private static long? ReadLong(JsonElement element) => element.ValueKind switch { JsonValueKind.Number when element.TryGetInt64(out var number) => number, JsonValueKind.String when long.TryParse(element.GetString(), out var parsed) => parsed, _ => null, }; private static bool IsTrue(JsonElement element) => element.ValueKind switch { JsonValueKind.True => true, JsonValueKind.False => false, JsonValueKind.Number when element.TryGetInt64(out var number) => number != 0, JsonValueKind.String => string.Equals(element.GetString()?.Trim(), "true", StringComparison.OrdinalIgnoreCase) || element.GetString()?.Trim() == "1", _ => false, }; }