Files
AzuTear 639b0b7f59 feat(scanner): add native artifact pipeline
Add native IK-style capture processing, Artifact Inventory, explicit promotion and single-result review. Confirm the three live OCR corrections in the eval corpus and preserve extraction/value separation.
2026-07-09 23:30:42 +02:00

275 lines
10 KiB
C#

using System.Text.Json;
namespace GenshinAssistant.InputHelper;
internal static class IkInventoryLists
{
public static IkInventoryListStatus Load(string dataDir)
{
var dir = ResolveDirectory(dataDir);
var required = new[] { "artifacts.json", "weapons.json", "characters.json", "materials.json", "version.txt" };
var missing = required.Where(file => !File.Exists(Path.Combine(dir, file))).ToArray();
var status = new IkInventoryListStatus { Directory = dir, Missing = missing };
if (missing.Length > 0) return status;
status.Version = File.ReadAllText(Path.Combine(dir, "version.txt")).Trim();
status.ArtifactSets = CountJsonObjectProperties(Path.Combine(dir, "artifacts.json"));
status.ArtifactPieces = CountArtifactPieces(Path.Combine(dir, "artifacts.json"));
status.Weapons = CountJsonObjectProperties(Path.Combine(dir, "weapons.json"));
status.Characters = CountJsonObjectProperties(Path.Combine(dir, "characters.json"));
status.Materials = CountJsonObjectProperties(Path.Combine(dir, "materials.json"));
return status;
}
private static string ResolveDirectory(string dataDir)
{
if (!string.IsNullOrWhiteSpace(dataDir)) return dataDir;
var candidates = new List<string>();
var envDir = Environment.GetEnvironmentVariable("IK_INVENTORYLISTS_DIR");
if (!string.IsNullOrWhiteSpace(envDir)) candidates.Add(envDir);
AddDirectoryCandidates(candidates, AppContext.BaseDirectory);
AddDirectoryCandidates(candidates, Environment.CurrentDirectory);
var baseParent = Directory.GetParent(AppContext.BaseDirectory);
for (var depth = 0; depth < 6 && baseParent != null; depth++)
{
AddDirectoryCandidates(candidates, baseParent.FullName);
baseParent = baseParent.Parent;
}
return candidates.FirstOrDefault(IsCompleteInventoryListDirectory)
?? Path.Combine(AppContext.BaseDirectory, "inventorylists");
}
private static void AddDirectoryCandidates(List<string> candidates, string root)
{
if (string.IsNullOrWhiteSpace(root)) return;
candidates.Add(Path.Combine(root, "inventorylists"));
candidates.Add(Path.Combine(root, "ik-inventorylists"));
candidates.Add(Path.Combine(root, "data", "ik-inventorylists"));
}
private static bool IsCompleteInventoryListDirectory(string dir)
{
return File.Exists(Path.Combine(dir, "artifacts.json"))
&& File.Exists(Path.Combine(dir, "weapons.json"))
&& File.Exists(Path.Combine(dir, "characters.json"))
&& File.Exists(Path.Combine(dir, "materials.json"))
&& File.Exists(Path.Combine(dir, "version.txt"));
}
public static object CatalogPayload(string dataDir)
{
var status = Load(dataDir);
if (!status.Valid)
{
return new
{
data = status.ToPayload(),
artifacts = Array.Empty<object>(),
weapons = Array.Empty<object>(),
characters = Array.Empty<object>(),
materials = Array.Empty<object>(),
};
}
return new
{
data = status.ToPayload(),
artifacts = LoadArtifactCatalog(Path.Combine(status.Directory, "artifacts.json")),
weapons = LoadStringMapCatalog(Path.Combine(status.Directory, "weapons.json")),
characters = LoadCharacterCatalog(Path.Combine(status.Directory, "characters.json")),
materials = LoadStringMapCatalog(Path.Combine(status.Directory, "materials.json")),
};
}
private static int CountJsonObjectProperties(string path)
{
using var doc = JsonDocument.Parse(File.ReadAllText(path));
return doc.RootElement.ValueKind == JsonValueKind.Object
? doc.RootElement.EnumerateObject().Count()
: 0;
}
private static int CountArtifactPieces(string path)
{
using var doc = JsonDocument.Parse(File.ReadAllText(path));
if (doc.RootElement.ValueKind != JsonValueKind.Object) return 0;
var count = 0;
foreach (var set in doc.RootElement.EnumerateObject())
{
if (!set.Value.TryGetProperty("artifacts", out var artifacts)) continue;
if (artifacts.ValueKind == JsonValueKind.Object) count += artifacts.EnumerateObject().Count();
}
return count;
}
private static List<object> LoadStringMapCatalog(string path)
{
using var doc = JsonDocument.Parse(File.ReadAllText(path));
var entries = new List<object>();
if (doc.RootElement.ValueKind != JsonValueKind.Object) return entries;
foreach (var entry in doc.RootElement.EnumerateObject())
{
entries.Add(new
{
normalizedName = entry.Name,
good = entry.Value.ValueKind == JsonValueKind.String ? entry.Value.GetString() ?? "" : "",
});
}
return entries;
}
private static List<object> LoadCharacterCatalog(string path)
{
using var doc = JsonDocument.Parse(File.ReadAllText(path));
var entries = new List<object>();
if (doc.RootElement.ValueKind != JsonValueKind.Object) return entries;
foreach (var entry in doc.RootElement.EnumerateObject())
{
var value = entry.Value;
entries.Add(new
{
normalizedName = entry.Name,
good = JsonString(value, "GOOD"),
element = JsonFirstString(value, "Element"),
weaponType = JsonFirstInt(value, "WeaponType"),
constellationName = JsonFirstString(value, "ConstellationName"),
});
}
return entries;
}
private static List<object> LoadArtifactCatalog(string path)
{
using var doc = JsonDocument.Parse(File.ReadAllText(path));
var entries = new List<object>();
if (doc.RootElement.ValueKind != JsonValueKind.Object) return entries;
foreach (var set in doc.RootElement.EnumerateObject())
{
var pieces = new List<object>();
if (set.Value.TryGetProperty("artifacts", out var artifacts) && artifacts.ValueKind == JsonValueKind.Object)
{
foreach (var piece in artifacts.EnumerateObject())
{
pieces.Add(new
{
slot = piece.Name,
artifactName = JsonString(piece.Value, "artifactName"),
good = JsonString(piece.Value, "GOOD"),
normalizedName = JsonString(piece.Value, "normalizedName"),
});
}
}
entries.Add(new
{
normalizedName = set.Name,
setName = JsonString(set.Value, "setName"),
good = JsonString(set.Value, "GOOD"),
pieces,
});
}
return entries;
}
private static string JsonString(JsonElement value, string propertyName)
=> value.TryGetProperty(propertyName, out var property) && property.ValueKind == JsonValueKind.String
? property.GetString() ?? ""
: "";
private static string JsonFirstString(JsonElement value, string propertyName)
{
if (!value.TryGetProperty(propertyName, out var property)) return "";
if (property.ValueKind == JsonValueKind.String) return property.GetString() ?? "";
if (property.ValueKind == JsonValueKind.Array && property.GetArrayLength() > 0)
{
var first = property[0];
return first.ValueKind == JsonValueKind.String ? first.GetString() ?? "" : "";
}
return "";
}
private static int JsonFirstInt(JsonElement value, string propertyName)
{
if (!value.TryGetProperty(propertyName, out var property)) return -1;
if (property.ValueKind == JsonValueKind.Number && property.TryGetInt32(out var number)) return number;
if (property.ValueKind == JsonValueKind.Array && property.GetArrayLength() > 0)
{
var first = property[0];
return first.ValueKind == JsonValueKind.Number && first.TryGetInt32(out number) ? number : -1;
}
return -1;
}
}
internal sealed class IkInventoryListStatus
{
public string Directory { get; init; } = "";
public string Version { get; set; } = "";
public int ArtifactSets { get; set; }
public int ArtifactPieces { get; set; }
public int Weapons { get; set; }
public int Characters { get; set; }
public int Materials { get; set; }
public string[] Missing { get; init; } = Array.Empty<string>();
public bool Valid => Missing.Length == 0;
public object SupportedCategoriesPayload() => new
{
artifacts = ArtifactCategoryPayload("artifacts.json", ArtifactSets, ArtifactPieces),
weapons = SimpleCategoryPayload("weapons.json", Weapons),
characters = SimpleCategoryPayload("characters.json", Characters),
materials = SimpleCategoryPayload("materials.json", Materials),
};
private object ArtifactCategoryPayload(string file, int setCount, int pieceCount)
{
var catalogAvailable = Valid;
var nativeCaptureSupported = Valid;
return new
{
file,
setCount,
pieceCount,
supported = nativeCaptureSupported,
catalogAvailable,
nativeCaptureSupported,
scanStatus = nativeCaptureSupported ? "native_capture" : "missing_data",
};
}
private object SimpleCategoryPayload(string file, int count)
{
var catalogAvailable = Valid;
const bool nativeCaptureSupported = false;
return new
{
file,
count,
supported = nativeCaptureSupported,
catalogAvailable,
nativeCaptureSupported,
scanStatus = catalogAvailable ? "catalog_only" : "missing_data",
};
}
public object ToPayload() => new
{
directory = Directory,
version = Version,
artifactSets = ArtifactSets,
artifactPieces = ArtifactPieces,
weapons = Weapons,
characters = Characters,
materials = Materials,
totalEntries = ArtifactPieces + Weapons + Characters + Materials,
categories = SupportedCategoriesPayload(),
missing = Missing,
valid = Valid,
source = "InventoryKamera inventorylists",
};
}