-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEmbeddedItemDatabase.cs
More file actions
113 lines (90 loc) · 5.18 KB
/
Copy pathEmbeddedItemDatabase.cs
File metadata and controls
113 lines (90 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
using System.Collections.Frozen;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using RustPlusBot.Features.ItemData.Data;
using RustPlusBot.Features.ItemData.Lookup;
namespace RustPlusBot.Features.ItemData;
/// <summary>Loads the embedded <c>item-data.json</c> once and serves lookups. Singleton.</summary>
public sealed class EmbeddedItemDatabase : IItemDatabase
{
private const int ExpectedSchemaVersion = 5;
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
PropertyNameCaseInsensitive = true, NumberHandling = JsonNumberHandling.AllowReadingFromString,
};
private static readonly ItemDataset Dataset = Load();
private static readonly FrozenDictionary<int, ItemRecord> ById = IndexById(Dataset.Items);
private static readonly IReadOnlyList<RaidTarget> Raid = Dataset.RaidTargets ?? [];
private static readonly FrozenDictionary<int, RaidTarget> RaidById = IndexRaidById(Raid);
private static readonly IReadOnlyList<Smelter> Smelters = Dataset.Smelters ?? [];
private static readonly FrozenDictionary<int, Smelter> SmelterById = IndexSmelterById(Smelters);
private static readonly IReadOnlyList<CctvMonument> CctvList = Dataset.Cctv ?? [];
/// <inheritdoc />
public DatasetSources Sources => Dataset.Sources;
/// <inheritdoc />
public ItemRecord? GetById(int id) => ById.GetValueOrDefault(id);
/// <inheritdoc />
public ItemMatch Resolve(string query) => ItemLookup.Resolve(query, GetById, Dataset.Items);
/// <inheritdoc />
public RaidMatch ResolveRaidTarget(string query) => RaidLookup.Resolve(query, RaidById.GetValueOrDefault, Raid);
/// <inheritdoc />
public SmeltMatch ResolveSmelter(string query) =>
SmeltLookup.Resolve(query, SmelterById.GetValueOrDefault, Smelters);
/// <inheritdoc />
public IReadOnlyList<CctvMonument> CctvMonuments => CctvList;
/// <inheritdoc />
public CctvMatch ResolveCctv(string query) => CctvLookup.Resolve(query, CctvList);
/// <summary>
/// Deserializes and validates an item dataset from <paramref name="stream"/>.
/// Throws <see cref="InvalidOperationException"/> when the schema version does not match
/// <paramref name="expectedSchemaVersion"/>.
/// </summary>
/// <param name="stream">A readable stream containing the JSON dataset.</param>
/// <param name="expectedSchemaVersion">The schema version the caller expects.</param>
/// <returns>The validated <see cref="ItemDataset"/>.</returns>
/// <exception cref="InvalidOperationException">
/// The stream deserializes to null, or the schema version does not match <paramref name="expectedSchemaVersion"/>.
/// </exception>
internal static ItemDataset Parse(Stream stream, int expectedSchemaVersion)
{
var dataset = JsonSerializer.Deserialize<ItemDataset>(stream, JsonOptions)
?? throw new InvalidOperationException("item-data.json deserialized to null.");
if (dataset.SchemaVersion != expectedSchemaVersion)
{
throw new InvalidOperationException(
$"item-data.json schema version {dataset.SchemaVersion} != expected {expectedSchemaVersion}.");
}
return dataset;
}
/// <summary>
/// Builds a frozen id→item index from <paramref name="items"/>.
/// When the same id appears more than once the last entry wins.
/// </summary>
/// <param name="items">The full item list.</param>
/// <returns>A frozen dictionary keyed by item id.</returns>
internal static FrozenDictionary<int, ItemRecord> IndexById(IReadOnlyList<ItemRecord> items) =>
items.GroupBy(i => i.Id).ToFrozenDictionary(g => g.Key, g => g.Last());
/// <summary>Builds a frozen id→target index from the item-kind raid targets.</summary>
/// <param name="targets">All raid targets.</param>
/// <returns>A frozen dictionary keyed by item id (item-kind targets only).</returns>
internal static FrozenDictionary<int, RaidTarget> IndexRaidById(IReadOnlyList<RaidTarget> targets) =>
targets.Where(t => t.Kind == RaidTargetKind.Item)
.GroupBy(t => int.Parse(t.Key, CultureInfo.InvariantCulture))
.ToFrozenDictionary(g => g.Key, g => g.Last());
/// <summary>Builds a frozen id→smelter index keyed by the smelter's item id.</summary>
/// <param name="smelters">All smelters.</param>
/// <returns>A frozen dictionary keyed by smelter item id.</returns>
internal static FrozenDictionary<int, Smelter> IndexSmelterById(IReadOnlyList<Smelter> smelters) =>
smelters.GroupBy(s => int.Parse(s.Key, CultureInfo.InvariantCulture))
.ToFrozenDictionary(g => g.Key, g => g.Last());
private static ItemDataset Load()
{
var assembly = typeof(EmbeddedItemDatabase).Assembly;
var resourceName = assembly.GetManifestResourceNames()
.Single(n => n.EndsWith("item-data.json", StringComparison.Ordinal));
using var stream = assembly.GetManifestResourceStream(resourceName)
?? throw new InvalidOperationException("Embedded item-data.json not found.");
return Parse(stream, ExpectedSchemaVersion);
}
}