AI: Loadout page — ZeroSpace/ZeroSpace.Build/Program.cs
This commit is contained in:
@@ -72,6 +72,8 @@ if (skipped.Count > 0)
|
|||||||
var starSystems = new List<StarSystemSource>();
|
var starSystems = new List<StarSystemSource>();
|
||||||
var systemMasteries = new List<SystemMasterySource>();
|
var systemMasteries = new List<SystemMasterySource>();
|
||||||
var planetaryMasteryItems = new List<PlanetaryMasteryItemSource>();
|
var planetaryMasteryItems = new List<PlanetaryMasteryItemSource>();
|
||||||
|
var generalItems = new List<GeneralItemSource>();
|
||||||
|
var factionPowerItems = new List<FactionPowerItemSource>();
|
||||||
var skippedDocs = new List<string>();
|
var skippedDocs = new List<string>();
|
||||||
var usedIds = new HashSet<string>(StringComparer.Ordinal);
|
var usedIds = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
|
||||||
@@ -116,55 +118,74 @@ foreach (var path in Directory.EnumerateFiles(docsRoot, "*.md", SearchOption.All
|
|||||||
skippedDocs.Add($"{Path.GetFileName(path)} ({reason})");
|
skippedDocs.Add($"{Path.GetFileName(path)} ({reason})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (string.Equals(categoryValue.Scalar, "GeneralItem", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (TryReadGeneralItem(path, frontMatter, body, usedIds, out var generalItem, out var reason))
|
||||||
|
{
|
||||||
|
generalItems.Add(generalItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
skippedDocs.Add($"{Path.GetFileName(path)} ({reason})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (string.Equals(categoryValue.Scalar, "FactionPowerItem", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (TryReadFactionPowerItem(path, frontMatter, body, usedIds, out var factionPowerItem, out var reason))
|
||||||
|
{
|
||||||
|
factionPowerItems.Add(factionPowerItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
skippedDocs.Add($"{Path.GetFileName(path)} ({reason})");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
starSystems.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
|
starSystems.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
|
||||||
systemMasteries.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
|
systemMasteries.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
|
||||||
planetaryMasteryItems.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
|
planetaryMasteryItems.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
generalItems.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
factionPowerItems.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
var starSystemsPath = Path.Combine(modelRoot, "StarSystems.g.cs");
|
var starSystemsPath = Path.Combine(modelRoot, "StarSystems.g.cs");
|
||||||
var systemMasteriesPath = Path.Combine(modelRoot, "SystemMasteries.g.cs");
|
var systemMasteriesPath = Path.Combine(modelRoot, "SystemMasteries.g.cs");
|
||||||
var planetaryMasteryItemsPath = Path.Combine(modelRoot, "PlanetaryMasteryItems.g.cs");
|
var planetaryMasteryItemsPath = Path.Combine(modelRoot, "PlanetaryMasteryItems.g.cs");
|
||||||
|
var generalItemsPath = Path.Combine(modelRoot, "GeneralItems.g.cs");
|
||||||
|
var factionPowerItemsPath = Path.Combine(modelRoot, "FactionPowerItems.g.cs");
|
||||||
|
|
||||||
File.WriteAllText(starSystemsPath, GenerateStarSystems(starSystems), Encoding.UTF8);
|
File.WriteAllText(starSystemsPath, GenerateStarSystems(starSystems), Encoding.UTF8);
|
||||||
File.WriteAllText(systemMasteriesPath, GenerateSystemMasteries(systemMasteries), Encoding.UTF8);
|
File.WriteAllText(systemMasteriesPath, GenerateSystemMasteries(systemMasteries), Encoding.UTF8);
|
||||||
File.WriteAllText(planetaryMasteryItemsPath, GeneratePlanetaryMasteryItems(planetaryMasteryItems), Encoding.UTF8);
|
File.WriteAllText(planetaryMasteryItemsPath, GeneratePlanetaryMasteryItems(planetaryMasteryItems), Encoding.UTF8);
|
||||||
|
File.WriteAllText(generalItemsPath, GenerateGeneralItems(generalItems), Encoding.UTF8);
|
||||||
|
File.WriteAllText(factionPowerItemsPath, GenerateFactionPowerItems(factionPowerItems), Encoding.UTF8);
|
||||||
|
|
||||||
Console.WriteLine($"Generated {starSystems.Count} star systems: {Path.GetRelativePath(solutionRoot, starSystemsPath)}");
|
Console.WriteLine($"Generated {starSystems.Count} star systems: {Path.GetRelativePath(solutionRoot, starSystemsPath)}");
|
||||||
Console.WriteLine($"Generated {systemMasteries.Count} system masteries: {Path.GetRelativePath(solutionRoot, systemMasteriesPath)}");
|
Console.WriteLine($"Generated {systemMasteries.Count} system masteries: {Path.GetRelativePath(solutionRoot, systemMasteriesPath)}");
|
||||||
Console.WriteLine($"Generated {planetaryMasteryItems.Count} planetary mastery items: {Path.GetRelativePath(solutionRoot, planetaryMasteryItemsPath)}");
|
Console.WriteLine($"Generated {planetaryMasteryItems.Count} planetary mastery items: {Path.GetRelativePath(solutionRoot, planetaryMasteryItemsPath)}");
|
||||||
|
Console.WriteLine($"Generated {generalItems.Count} general items: {Path.GetRelativePath(solutionRoot, generalItemsPath)}");
|
||||||
|
Console.WriteLine($"Generated {factionPowerItems.Count} faction power items: {Path.GetRelativePath(solutionRoot, factionPowerItemsPath)}");
|
||||||
|
|
||||||
// Copy item icons next to the markdown docs into the web app's wwwroot so the
|
// Copy item icons next to the markdown docs into the web app's wwwroot so the
|
||||||
// items page can reference them. Images are copied, never moved: the docs folder
|
// pages can reference them. Images are copied, never moved: the docs folder
|
||||||
// stays the source of truth.
|
// stays the source of truth.
|
||||||
var itemImagesDir = Path.Combine(solutionRoot, "ZeroSpace.Web", "wwwroot", "images", "planetary-mastery-items");
|
var webImagesRoot = Path.Combine(solutionRoot, "ZeroSpace.Web", "wwwroot", "images");
|
||||||
Directory.CreateDirectory(itemImagesDir);
|
var copiedPlanetaryImages = SyncItemImages(
|
||||||
|
planetaryMasteryItems.Select(static item => (item.ImageFileName, item.ImageSourcePath)),
|
||||||
|
Path.Combine(webImagesRoot, "planetary-mastery-items"),
|
||||||
|
solutionRoot);
|
||||||
|
var copiedGeneralImages = SyncItemImages(
|
||||||
|
generalItems.Select(static item => (item.ImageFileName, item.ImageSourcePath)),
|
||||||
|
Path.Combine(webImagesRoot, "general-items"),
|
||||||
|
solutionRoot);
|
||||||
|
var copiedFactionPowerImages = SyncItemImages(
|
||||||
|
factionPowerItems.Select(static item => (item.ImageFileName, item.ImageSourcePath)),
|
||||||
|
Path.Combine(webImagesRoot, "faction-power-items"),
|
||||||
|
solutionRoot);
|
||||||
|
|
||||||
var expectedImages = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
Console.WriteLine($"Copied {copiedPlanetaryImages} planetary mastery item images to {Path.GetRelativePath(solutionRoot, Path.Combine(webImagesRoot, "planetary-mastery-items"))}");
|
||||||
var copiedImages = 0;
|
Console.WriteLine($"Copied {copiedGeneralImages} general item images to {Path.GetRelativePath(solutionRoot, Path.Combine(webImagesRoot, "general-items"))}");
|
||||||
foreach (var item in planetaryMasteryItems)
|
Console.WriteLine($"Copied {copiedFactionPowerImages} faction power item images to {Path.GetRelativePath(solutionRoot, Path.Combine(webImagesRoot, "faction-power-items"))}");
|
||||||
{
|
|
||||||
if (item.ImageFileName is null || item.ImageSourcePath is null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedImages.Add(item.ImageFileName);
|
|
||||||
var destination = Path.Combine(itemImagesDir, item.ImageFileName);
|
|
||||||
File.Copy(item.ImageSourcePath, destination, overwrite: true);
|
|
||||||
copiedImages++;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var stale in Directory.EnumerateFiles(itemImagesDir, "*.png"))
|
|
||||||
{
|
|
||||||
if (!expectedImages.Contains(Path.GetFileName(stale)))
|
|
||||||
{
|
|
||||||
File.Delete(stale);
|
|
||||||
Console.WriteLine($"Removed stale item image: {Path.GetRelativePath(solutionRoot, stale)}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine($"Copied {copiedImages} planetary mastery item images to {Path.GetRelativePath(solutionRoot, itemImagesDir)}");
|
|
||||||
if (skippedDocs.Count > 0)
|
if (skippedDocs.Count > 0)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Skipped {skippedDocs.Count} incomplete docs:");
|
Console.WriteLine($"Skipped {skippedDocs.Count} incomplete docs:");
|
||||||
@@ -754,6 +775,102 @@ static bool TryReadPlanetaryMasteryItem(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool TryReadGeneralItem(
|
||||||
|
string path,
|
||||||
|
IReadOnlyDictionary<string, FrontMatterValue> frontMatter,
|
||||||
|
string body,
|
||||||
|
HashSet<string> usedIds,
|
||||||
|
out GeneralItemSource item,
|
||||||
|
out string reason)
|
||||||
|
{
|
||||||
|
item = default!;
|
||||||
|
|
||||||
|
// General items have a variable number of levels (e.g. Renewal Matrix has
|
||||||
|
// two); collect whichever level_1..level_5 entries are present.
|
||||||
|
var levels = ReadOptionalLevels(frontMatter);
|
||||||
|
if (levels.Count == 0)
|
||||||
|
{
|
||||||
|
reason = "missing level_1";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = Path.GetFileNameWithoutExtension(path);
|
||||||
|
var (imageFileName, imageSourcePath) = FindItemImage(path);
|
||||||
|
|
||||||
|
item = new GeneralItemSource(
|
||||||
|
ToUniqueIdentifier(name, usedIds),
|
||||||
|
name,
|
||||||
|
levels,
|
||||||
|
TryGetOptionalString(frontMatter, "limitedToMerc"),
|
||||||
|
imageFileName,
|
||||||
|
imageSourcePath,
|
||||||
|
body);
|
||||||
|
reason = string.Empty;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool TryReadFactionPowerItem(
|
||||||
|
string path,
|
||||||
|
IReadOnlyDictionary<string, FrontMatterValue> frontMatter,
|
||||||
|
string body,
|
||||||
|
HashSet<string> usedIds,
|
||||||
|
out FactionPowerItemSource item,
|
||||||
|
out string reason)
|
||||||
|
{
|
||||||
|
item = default!;
|
||||||
|
|
||||||
|
var levels = ReadOptionalLevels(frontMatter);
|
||||||
|
if (levels.Count == 0)
|
||||||
|
{
|
||||||
|
reason = "missing level_1";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = Path.GetFileNameWithoutExtension(path);
|
||||||
|
var (imageFileName, imageSourcePath) = FindItemImage(path);
|
||||||
|
|
||||||
|
item = new FactionPowerItemSource(
|
||||||
|
ToUniqueIdentifier(name, usedIds),
|
||||||
|
name,
|
||||||
|
levels,
|
||||||
|
TryGetOptionalString(frontMatter, "limitedTo"),
|
||||||
|
TryGetOptionalString(frontMatter, "limitedToMerc"),
|
||||||
|
TryGetOptionalInt(frontMatter, "minimumLevel"),
|
||||||
|
imageFileName,
|
||||||
|
imageSourcePath,
|
||||||
|
body);
|
||||||
|
reason = string.Empty;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IReadOnlyList<string> ReadOptionalLevels(IReadOnlyDictionary<string, FrontMatterValue> frontMatter)
|
||||||
|
{
|
||||||
|
var levels = new List<string>();
|
||||||
|
for (var level = 1; level <= 5; level++)
|
||||||
|
{
|
||||||
|
var text = TryGetOptionalString(frontMatter, $"level_{level}");
|
||||||
|
if (!string.IsNullOrWhiteSpace(text))
|
||||||
|
{
|
||||||
|
levels.Add(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return levels;
|
||||||
|
}
|
||||||
|
|
||||||
|
static (string? FileName, string? SourcePath) FindItemImage(string path)
|
||||||
|
{
|
||||||
|
// An icon with the same file name as the doc (e.g. "Battery.png" next to
|
||||||
|
// "Battery.md") is treated as this item's image when present.
|
||||||
|
var candidateImagePath = Path.ChangeExtension(path, ".png");
|
||||||
|
if (File.Exists(candidateImagePath))
|
||||||
|
{
|
||||||
|
return (Path.GetFileName(candidateImagePath), candidateImagePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
static bool IsMasteryField(string key)
|
static bool IsMasteryField(string key)
|
||||||
{
|
{
|
||||||
if (string.Equals(key, "category", StringComparison.OrdinalIgnoreCase) ||
|
if (string.Equals(key, "category", StringComparison.OrdinalIgnoreCase) ||
|
||||||
@@ -896,11 +1013,122 @@ static string GeneratePlanetaryMasteryItems(IReadOnlyList<PlanetaryMasteryItemSo
|
|||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static string GenerateGeneralItems(IReadOnlyList<GeneralItemSource> items)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine("// <auto-generated />");
|
||||||
|
builder.AppendLine("namespace ZeroSpace.Model;");
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine("public static class GeneralItems");
|
||||||
|
builder.AppendLine("{");
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
builder.AppendLine($" public static readonly GeneralItemData {item.Id} = new(");
|
||||||
|
builder.AppendLine($" Id: {Literal(item.Id)},");
|
||||||
|
builder.AppendLine($" Name: {Literal(item.Name)},");
|
||||||
|
builder.AppendLine($" Levels: [{string.Join(", ", item.Levels.Select(Literal))}],");
|
||||||
|
builder.AppendLine($" LimitedToMerc: {Literal(item.LimitedToMerc)},");
|
||||||
|
builder.AppendLine($" ImageFileName: {Literal(item.ImageFileName)},");
|
||||||
|
builder.AppendLine($" Body: {Literal(item.Body)});");
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine(" public static IReadOnlyList<GeneralItemData> All { get; } =");
|
||||||
|
builder.AppendLine(" [");
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
builder.AppendLine($" {item.Id},");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine(" ];");
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine(" public static IReadOnlyDictionary<string, GeneralItemData> ByName { get; } =");
|
||||||
|
builder.AppendLine(" All.ToDictionary(static item => item.Name, StringComparer.OrdinalIgnoreCase);");
|
||||||
|
builder.AppendLine("}");
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static string GenerateFactionPowerItems(IReadOnlyList<FactionPowerItemSource> items)
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine("// <auto-generated />");
|
||||||
|
builder.AppendLine("namespace ZeroSpace.Model;");
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine("public static class FactionPowerItems");
|
||||||
|
builder.AppendLine("{");
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
builder.AppendLine($" public static readonly FactionPowerItemData {item.Id} = new(");
|
||||||
|
builder.AppendLine($" Id: {Literal(item.Id)},");
|
||||||
|
builder.AppendLine($" Name: {Literal(item.Name)},");
|
||||||
|
builder.AppendLine($" Levels: [{string.Join(", ", item.Levels.Select(Literal))}],");
|
||||||
|
builder.AppendLine($" LimitedTo: {Literal(item.LimitedTo)},");
|
||||||
|
builder.AppendLine($" LimitedToMerc: {Literal(item.LimitedToMerc)},");
|
||||||
|
builder.AppendLine($" MinimumLevel: {item.MinimumLevel?.ToString(CultureInfo.InvariantCulture) ?? "null"},");
|
||||||
|
builder.AppendLine($" ImageFileName: {Literal(item.ImageFileName)},");
|
||||||
|
builder.AppendLine($" Body: {Literal(item.Body)});");
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine(" public static IReadOnlyList<FactionPowerItemData> All { get; } =");
|
||||||
|
builder.AppendLine(" [");
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
builder.AppendLine($" {item.Id},");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine(" ];");
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine(" public static IReadOnlyDictionary<string, FactionPowerItemData> ByName { get; } =");
|
||||||
|
builder.AppendLine(" All.ToDictionary(static item => item.Name, StringComparer.OrdinalIgnoreCase);");
|
||||||
|
builder.AppendLine("}");
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
static string BoolLiteral(bool value)
|
static string BoolLiteral(bool value)
|
||||||
{
|
{
|
||||||
return value ? "true" : "false";
|
return value ? "true" : "false";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int SyncItemImages(
|
||||||
|
IEnumerable<(string? FileName, string? SourcePath)> items,
|
||||||
|
string imagesDir,
|
||||||
|
string solutionRoot)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(imagesDir);
|
||||||
|
|
||||||
|
var expectedImages = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var copiedImages = 0;
|
||||||
|
foreach (var (fileName, sourcePath) in items)
|
||||||
|
{
|
||||||
|
if (fileName is null || sourcePath is null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedImages.Add(fileName);
|
||||||
|
File.Copy(sourcePath, Path.Combine(imagesDir, fileName), overwrite: true);
|
||||||
|
copiedImages++;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var stale in Directory.EnumerateFiles(imagesDir, "*.png"))
|
||||||
|
{
|
||||||
|
if (!expectedImages.Contains(Path.GetFileName(stale)))
|
||||||
|
{
|
||||||
|
File.Delete(stale);
|
||||||
|
Console.WriteLine($"Removed stale item image: {Path.GetRelativePath(solutionRoot, stale)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copiedImages;
|
||||||
|
}
|
||||||
|
|
||||||
static double GetDouble(IReadOnlyDictionary<string, FrontMatterValue> frontMatter, string key)
|
static double GetDouble(IReadOnlyDictionary<string, FrontMatterValue> frontMatter, string key)
|
||||||
{
|
{
|
||||||
if (frontMatter.TryGetValue(key, out var value) &&
|
if (frontMatter.TryGetValue(key, out var value) &&
|
||||||
@@ -971,3 +1199,23 @@ internal sealed record PlanetaryMasteryItemSource(
|
|||||||
string? ImageFileName,
|
string? ImageFileName,
|
||||||
string? ImageSourcePath,
|
string? ImageSourcePath,
|
||||||
string Body);
|
string Body);
|
||||||
|
|
||||||
|
internal sealed record GeneralItemSource(
|
||||||
|
string Id,
|
||||||
|
string Name,
|
||||||
|
IReadOnlyList<string> Levels,
|
||||||
|
string? LimitedToMerc,
|
||||||
|
string? ImageFileName,
|
||||||
|
string? ImageSourcePath,
|
||||||
|
string Body);
|
||||||
|
|
||||||
|
internal sealed record FactionPowerItemSource(
|
||||||
|
string Id,
|
||||||
|
string Name,
|
||||||
|
IReadOnlyList<string> Levels,
|
||||||
|
string? LimitedTo,
|
||||||
|
string? LimitedToMerc,
|
||||||
|
int? MinimumLevel,
|
||||||
|
string? ImageFileName,
|
||||||
|
string? ImageSourcePath,
|
||||||
|
string Body);
|
||||||
|
|||||||
Reference in New Issue
Block a user