Skip to content

Commit ef7f6f8

Browse files
HandyS11claude
andcommitted
feat(generator): load decay + upkeep from rustlabs files (6b)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4f414cf commit ef7f6f8

4 files changed

Lines changed: 146 additions & 3 deletions

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using RustPlusBot.ItemData.Generator.Sources;
2+
3+
namespace RustPlusBot.ItemData.Generator.Tests;
4+
5+
public sealed class OfflineRustLabsSourceTests
6+
{
7+
private static OfflineRustLabsSource SourceWith(string decayJson, string upkeepJson)
8+
{
9+
var decayPath = Path.GetTempFileName();
10+
var upkeepPath = Path.GetTempFileName();
11+
File.WriteAllText(decayPath, decayJson);
12+
File.WriteAllText(upkeepPath, upkeepJson);
13+
// Recycle/craft/research paths are unused by these two loaders; point them at the upkeep temp file.
14+
return new OfflineRustLabsSource(upkeepPath, upkeepPath, upkeepPath, decayPath, upkeepPath);
15+
}
16+
17+
[Fact]
18+
public void LoadDecay_readsItemsWrapper_andSecondsAndHp()
19+
{
20+
const string decay =
21+
"""{"items":{"15388698":{"decay":900,"decayString":"15 min","decayOutside":null,"decayInside":null,"decayUnderwater":null,"hp":100,"hpString":"100"}}}""";
22+
var result = SourceWith(decay, """{"items":{}}""").LoadDecay();
23+
24+
Assert.True(result.ContainsKey(15388698));
25+
Assert.Equal(900, result[15388698].Seconds);
26+
Assert.Equal(100, result[15388698].Hp);
27+
Assert.Null(result[15388698].OutsideSeconds);
28+
}
29+
30+
[Fact]
31+
public void LoadUpkeep_readsItemsWrapper_andParsesRange()
32+
{
33+
const string upkeep =
34+
"""{"items":{"1729120840":[{"id":"-151838493","quantity":"8–25"}]}}""";
35+
var result = SourceWith("""{"items":{}}""", upkeep).LoadUpkeep();
36+
37+
Assert.True(result.ContainsKey(1729120840));
38+
var entry = Assert.Single(result[1729120840].Entries);
39+
Assert.Equal(-151838493, entry.ItemId);
40+
Assert.Equal(8, entry.QuantityMin);
41+
Assert.Equal(25, entry.QuantityMax);
42+
}
43+
44+
[Fact]
45+
public void LoadUpkeep_singleQuantity_hasEqualMinMax()
46+
{
47+
const string upkeep = """{"items":{"671706427":[{"id":"317398316","quantity":"1"}]}}""";
48+
var entry = Assert.Single(SourceWith("""{"items":{}}""", upkeep).LoadUpkeep()[671706427].Entries);
49+
Assert.Equal(1, entry.QuantityMin);
50+
Assert.Equal(1, entry.QuantityMax);
51+
}
52+
}

tools/RustPlusBot.ItemData.Generator/Program.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ internal static int Main(string[] args)
6060
var rustLabsSource = new OfflineRustLabsSource(
6161
Path.Combine(rustplusDir, "rustlabsRecycleData.json"),
6262
Path.Combine(rustplusDir, "rustlabsCraftData.json"),
63-
Path.Combine(rustplusDir, "rustlabsResearchData.json"));
63+
Path.Combine(rustplusDir, "rustlabsResearchData.json"),
64+
Path.Combine(rustplusDir, "rustlabsDecayData.json"),
65+
Path.Combine(rustplusDir, "rustlabsUpkeepData.json"));
6466

6567
var names = namesSource.LoadNames();
6668
Console.WriteLine($"Loaded {names.Count} names from items.json");

tools/RustPlusBot.ItemData.Generator/Sources/IRustLabsSource.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,10 @@ internal interface IRustLabsSource
1313

1414
/// <summary>Loads research costs keyed by item id.</summary>
1515
IReadOnlyDictionary<int, ResearchCost> LoadResearchCosts();
16+
17+
/// <summary>Loads decay info keyed by item id.</summary>
18+
IReadOnlyDictionary<int, DecayInfo> LoadDecay();
19+
20+
/// <summary>Loads upkeep costs keyed by item id.</summary>
21+
IReadOnlyDictionary<int, UpkeepCost> LoadUpkeep();
1622
}

tools/RustPlusBot.ItemData.Generator/Sources/OfflineRustLabsSource.cs

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@
44

55
namespace RustPlusBot.ItemData.Generator.Sources;
66

7-
/// <summary>Reads recycle, craft, and research data from the offline RustLabs JSON files.</summary>
7+
/// <summary>Reads recycle, craft, research, decay, and upkeep data from the offline RustLabs JSON files.</summary>
88
/// <param name="RecycleFilePath">Path to the <c>rustlabsRecycleData.json</c> file.</param>
99
/// <param name="CraftFilePath">Path to the <c>rustlabsCraftData.json</c> file.</param>
1010
/// <param name="ResearchFilePath">Path to the <c>rustlabsResearchData.json</c> file.</param>
11+
/// <param name="DecayFilePath">Path to the <c>rustlabsDecayData.json</c> file.</param>
12+
/// <param name="UpkeepFilePath">Path to the <c>rustlabsUpkeepData.json</c> file.</param>
1113
internal sealed class OfflineRustLabsSource(
1214
string RecycleFilePath,
1315
string CraftFilePath,
14-
string ResearchFilePath) : IRustLabsSource
16+
string ResearchFilePath,
17+
string DecayFilePath,
18+
string UpkeepFilePath) : IRustLabsSource
1519
{
1620
private static readonly Dictionary<string, int> WorkbenchLevels = new()
1721
{
@@ -160,4 +164,83 @@ public IReadOnlyDictionary<int, ResearchCost> LoadResearchCosts()
160164

161165
return result;
162166
}
167+
168+
/// <inheritdoc/>
169+
public IReadOnlyDictionary<int, DecayInfo> LoadDecay()
170+
{
171+
using var stream = File.OpenRead(DecayFilePath);
172+
using var doc = JsonDocument.Parse(stream);
173+
174+
var result = new Dictionary<int, DecayInfo>();
175+
if (!doc.RootElement.TryGetProperty("items", out var itemsEl))
176+
{
177+
return result;
178+
}
179+
180+
foreach (var prop in itemsEl.EnumerateObject())
181+
{
182+
if (!int.TryParse(prop.Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var id))
183+
{
184+
continue;
185+
}
186+
187+
result[id] = new DecayInfo(
188+
ReadInt(prop.Value, "decay"),
189+
ReadInt(prop.Value, "decayOutside"),
190+
ReadInt(prop.Value, "decayInside"),
191+
ReadInt(prop.Value, "decayUnderwater"),
192+
ReadInt(prop.Value, "hp"));
193+
}
194+
195+
return result;
196+
197+
static int? ReadInt(JsonElement element, string name) =>
198+
element.TryGetProperty(name, out var p) && p.ValueKind == JsonValueKind.Number
199+
? p.GetInt32()
200+
: null;
201+
}
202+
203+
/// <inheritdoc/>
204+
public IReadOnlyDictionary<int, UpkeepCost> LoadUpkeep()
205+
{
206+
using var stream = File.OpenRead(UpkeepFilePath);
207+
using var doc = JsonDocument.Parse(stream);
208+
209+
var result = new Dictionary<int, UpkeepCost>();
210+
if (!doc.RootElement.TryGetProperty("items", out var itemsEl))
211+
{
212+
return result;
213+
}
214+
215+
foreach (var prop in itemsEl.EnumerateObject())
216+
{
217+
if (!int.TryParse(prop.Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var id))
218+
{
219+
continue;
220+
}
221+
222+
var entries = new List<UpkeepEntry>();
223+
foreach (var entry in prop.Value.EnumerateArray())
224+
{
225+
var entryIdStr = entry.GetProperty("id").GetString();
226+
if (entryIdStr is null ||
227+
!int.TryParse(entryIdStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out var entryId))
228+
{
229+
continue;
230+
}
231+
232+
var quantity = entry.GetProperty("quantity").GetString()
233+
?? throw new InvalidOperationException($"upkeep {id}: null quantity");
234+
var (min, max) = UpkeepQuantity.Parse(quantity);
235+
entries.Add(new UpkeepEntry(entryId, min, max));
236+
}
237+
238+
if (entries.Count > 0)
239+
{
240+
result[id] = new UpkeepCost(entries);
241+
}
242+
}
243+
244+
return result;
245+
}
163246
}

0 commit comments

Comments
 (0)